­

被自己以為的GZIP秀到了

 

問題的開始

 我司某產品線有這麼一個神奇接口 (//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哈夫曼編碼的一個組合體( //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。

以下引至//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.

 

 

總結

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

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