天天看點

針對GZIP檔案類型的并行讀取

gzip是最常見的壓縮檔案格式,目前datax是支援對該壓縮檔案直接的讀取,但每個gzip僅僅隻能啟動一個線程來讀取,當gzip比較大,或者說針對gzip中的資料有着較複雜的操作的情況下,執行效率往往比較低下。下面就讨論下如何對針對gzip檔案類型的并行讀取,大幅度提高執行效率。

首先對一個gzip檔案進行解壓縮的測試:

該壓縮檔案大小為18mb,測試解壓縮的時間:

僅僅不到2秒就完成了對該檔案的解壓操作,看下解壓後的檔案情況:

解壓後檔案大小為301mb,這個壓縮率還是相當可觀的。一般來說,對于結構化的資料,壓縮率都比較高。這麼看來,解壓的效率還是很高的,不會成為性能的瓶頸,這也是我們接下來通過對gzip檔案并行讀取提高整體資料同步效率的前提。試想下,如果解壓縮的操作非常耗時,那麼并行讀取意義就不大了。

該檔案中共有将近63萬條記錄。

并行讀取的前提是讀取任務可以拆分,對于類似上面這樣結構化的檔案通過記錄數來拆分是最自然的想法。例如,上面的檔案存在628410行記錄,啟動10個并行的話,那麼每個線程讀取62841行記錄即可。但實際操作上卻非常困難,針對一個大檔案,想要準确定位到某一行絕對是件效率不高的操作。

記錄數不行,那就通過資料量(偏移量)吧,上面的檔案解壓縮後有301mb,如果啟動10個并行的話,每個線程讀取30mb左右的資料(不能通過壓縮後的18mb來做拆分,這并不是實際資料的大小)。通過java可以很容易的實作對壓縮檔案的流式讀取,邊讀邊解壓。這時候就要求我們在讀之間最好就可以知道該檔案壓縮前的大小。看下gzip的壓縮格式,在檔案的結尾部分是儲存了該資訊的:

比較悲催的一點是,isize存儲的是以2的32次方為模的值,也就是說當原始檔案大于4gb的時候,該值就不能準确的反應原始檔案的大小了。gzip的規範應該很久很久以前制定的,當時估計還不支援這麼大的檔案吧。這點先忽略吧,目前的項目中檔案都是相對較小的。

我們來驗證下,看看如何通過讀取gzip的檔案尾,來計算原始檔案大小。

對1.txt進行壓縮,并檢視壓縮後的檔案資訊:

···

[[email protected] /home/weiguang.sunwg/sunwg/test]

$gzip 1.txt

$xxd 1.txt.gz

0000000: 1f8b 0808 5b15 2959 0003 312e 7478 7400 ....[.)y..1.txt.

0000010: 4be4 0200 07a1 eadd 0200 0000 k...........

最後4個位元組為0200 0000,高低位轉換後為0000 0002,意思該壓縮檔案對應的原始檔案為2個位元組。

在看下前面那個壓縮後為18mb的檔案,檔案尾如下:

最後4個位元組為927d c012,高低位轉換後為12c0 7d92,轉換為10進制為314604946,即為解壓後檔案大小。

通過解析gzip檔案尾,可以很友善的得到該原始檔案大小,根據原始檔案大小可以比較友善的進行并行的拆分,并且基本保證每個并行處理的資料量差不多,避免長尾。

按資料量拆分,不能保證每個拆分點都是一行記錄的結尾,是以每個并行需要進行記錄對齊,保證讀取的是完整的一行。對齊的規則也相當簡單,開頭少讀半行,結尾多讀半行。示意圖如下:

針對GZIP檔案類型的并行讀取

該并行讀取資料頭和尾分别為a0和b0,根據上面的對齊規則調整為a1和b1,保證記錄對齊。其實針對其他結構化的檔案都可以如此操作,隻要有明确的行分隔符。

實作了對gzip檔案的并行讀取,就可以很容易的通過設定更高的并行度來提高同步的效率,更好的滿足使用者的需求。

繼續閱讀