被自己以為的GZIP秀到了

lulianqi15發表於2021-01-04

 

問題的開始

 我司某產品線有這麼一個神奇介面 (https://host/path/customQuery)

該介面在預發或線上快取正常的情況下TTFB為150ms左右(可以認為服務處理時間差不多就是TTFB),不過相比150ms的TTFB,顯然資料資源下載時間過長的問題會更引人注意需要100ms左右(當然這也是網路條優秀的情況下,網路一般的話這個下載時間會更誇張)
customQuery請求一次請求的資料響應大概為2.7MB, 壓縮後也有超過300KB
下載時間過長看起來就是因為這個響應實體過大了(100Mb的頻寬滿速,300KB差不多也需要30ms),通過測試可以發現同樣的網路條件同一個應用的其他介面,如果響應壓縮後小於1KB,其ContentDownLoad時間可以忽略不計(通常都會小於2ms)
因為代理預設開啟了gzip,其實資料已經被壓縮了近10倍,但是壓縮後的資料還是過大。
分析了customQuery響應實體的資料結構。
發現資料每個list中fields節點大量重複出現。

如上圖其中field的描述是完全一致的(按一頁50條計算,這些資料重複了50遍)
這些資料field描述資料單個都大小大概是50KB(重複50次可以看到2.7MB的資料幾乎都是這些重複的資料)

 

開始秀了

既然已經明確了這些重複描述資料,服務端的同學很自然想到把這些field描述提取出來重新組裝資料可以大幅度減小資料傳輸的大小。
不過自己恰好曾經“看過”DEFLATE壓縮(http的gzip正好使用的是DEFLATE)其中使用到的LZ77是會匹配前文相同短語後面的相同短語都會被替換成“標記”。
那我“秀”的時候又到了,當即表示採用這種資料重組的方式並不會帶來明顯的實際提升,因為資料實際的資訊量沒有實際變化,只是手動去除了冗餘,而之前冗餘的資料其實已經被gzip處理過了,所以僅僅單純去除重複描述資料片段並不能帶來預期的收益。
因為我秀的時候如此自信,對方馬上就自己不自信了,表示要回去先驗證效果後在做打算。

 

看起來是失敗了

果然後面的結果“居然”是我被打臉了

 

 

 customQuery介面返回的實體大小直接變成了25kb,解壓後189kb(之前是327kb,解壓後2.7Mb)

那這差距太大了,實體大小減小到了之前的10%不到,當然下載速度ContentDownLoad也有了大幅度的降低。(基本上就是一個RTT的時間)
不過這完全跟我之前的認知不一樣啊,一定是哪裡出現了問題。(畢竟是以為自己懂了系列)

 

試圖搶救下

為了挽回顏面,我把這2組原始資料下載下來,本地壓縮排行分析(還不想承認自己錯了,試圖找到產生這種結果的其他解釋)

如下圖老的資料為customQuery_v1(2.7MB),新的為customQuery_v2(190KB)

分別使用zip,gzip,rar對2組資料進行壓縮 (gzip即為http預設使用的壓縮演算法,MAC上直接使用gzip命令可以對檔案進行壓縮)
可以發現RAR的壓縮結果就與我最開始的想法差不多(即使原始資料差了超過10倍,而壓縮的結果是幾乎一致的,v1為19kb ;v2為17kb)
不過gzip對2組資料的壓縮結果與在瀏覽器上看到的是一樣的。(v1為329kb ;v2為25kb)
既然本地壓縮也得到了同樣的結果,看來真的是自己Too young too naive (大意了,沒有閃,秀的時候應該先在本地驗證一下的)

 

默默面對錯誤分析原因

但是為什麼會有這樣的結果,按我的理解壓縮結果應該與rar一致才對。要搞清楚還要從壓縮的方式入手。
一定是我以為的壓縮行為與實際存在差異,gzip的基礎是DEFLATE,DEFLATE是LZ77哈夫曼編碼的一個組合體( https://tools.ietf.org/html/rfc1951
Huffman Coding 只是單純的字元編碼,編碼後的大小與編碼前的大小直接正相關,肯定不是產生結果的原因。
那剩下就只有是LZ77,只能是LZ77一開始沒有把那些重複的fields壓縮掉,而為什麼LZ77沒有把原始資料裡大量重複的描述“標記”起來。
LZ77整體是是使用已經出現過的相應匹配資料資訊替換當前資料從而實現壓縮功能,為了匹配資料需要用到了“滑動視窗”的概念
細細一品,LZ77並不是全文匹配,資料為了可以邊傳送邊壓縮會進行分塊壓縮。通過查閱RFC文件,大概可以明確塊的大小被限制在64k內,最大滑動視窗就是64k/2=32k,並且還要求“標記”的最大長度為256位元組(當然標記長度這個問題不大,大不了不多用幾個標記)。這裡的問題在於使用滑動視窗就要求重複的資料必須要“相鄰” 而塊大小最大為64K,如果重複的2段資料不能出現在一個視窗內是不能被標記的。但是視窗最多是塊大小的一半32Kb(實際也不會用這麼大的視窗),而我們之前就計算過我們重複的單個field描述就有50Kb,要出現有2個重複的內容,即使2個描述相鄰那也至少上100Kb(他們甚至都無法在同一個塊裡),實際上視窗最大32Kb,所以LZ77根本不能標記出這些重複的field。

以下引至https://tools.ietf.org/html/rfc1951#section-2

Compressed representation overview

A compressed data set consists of a series of blocks, corresponding
to successive blocks of input data. The block sizes are arbitrary,
except that non-compressible blocks are limited to 65,535 bytes.

Each block is compressed using a combination of the LZ77 algorithm
and Huffman coding. The Huffman trees for each block are independent
of those for previous or subsequent blocks; the LZ77 algorithm may
use a reference to a duplicated string occurring in a previous block,
up to 32K input bytes before.

Each block consists of two parts: a pair of Huffman code trees that
describe the representation of the compressed data part, and a
compressed data part. (The Huffman trees themselves are compressed
using Huffman encoding.) The compressed data consists of a series of
elements of two types: literal bytes (of strings that have not been
detected as duplicated within the previous 32K input bytes), and
pointers to duplicated strings, where a pointer is represented as a
pair <length, backward distance>. The representation used in the
"deflate" format limits distances to 32K bytes and lengths to 258
bytes, but does not limit the size of a block, except for
uncompressible blocks, which are limited as noted above.

Each type of value (literals, distances, and lengths) in the
compressed data is represented using a Huffman code, using one code
tree for literals and lengths and a separate code tree for distances.
The code trees for each block appear in a compact form just before
the compressed data for that block.

 

 

總結

最終也還是自己錯了,也沒有什麼好總結的

要是什麼都不知道也不出問題,要是知道的很清楚也不會出問題,就是在“以為自己知道”的情況下就各種問題。

 

相關文章