個人感覺序列化簡單來說就是按一定規則組包。反序列化就是按組包時的規則來接包。正常來說。序列化不會很難。不會很複雜。因為過于複雜的序列化協定會導緻較長的解析時間,這可能會使得序列化和反序列化階段成為整個系統的瓶頸。就像壓縮檔案、解壓檔案,會占用大量cpu時間。
是以正常的序列化會在時間和空間上考慮。個人感覺對于電商業務時間應該是相對重要些。畢竟使用者沒有那麼多時間等你解析。
我們是用thrift來序列化的。一份thrift檔案生成2份。一份是c++生成的用來編寫服務接口。一份是php生成的。所有請求都會先落到前端機器。然後前端機器用php調用服務端函數接口。傳回處理結果。這其實是遠端調用rpc。
說序列化之前先說下平台給序列化配置設定的buf的空間大小
1、每個協程會配置設定大概固定標頭(56個位元組)+特殊buf(200個位元組)的空間來儲存標頭。是以首先如果收到的包特殊buf(就是放sessionkey和uid等資訊)大于200個位元組。會報錯不處理。但是并不會給netio 傳回一個錯誤包消息。是以用戶端 會一直等到用戶端設定的逾時時間
2、每個container會配置設定3M的空間來處理資料。是以去掉標頭和特殊buf.剩下的就是可以用來序列化的空間3*1024*1024-固定標頭-特殊buf。 是以最少會有 3*1024*1024-56-200的空間
這裡其實可以看到協程的好處。這個3M的空間。對于每個協程來說是共享的。因為我們是協程的方式,其實是一種順序流程,沒有協程會跟你競争使用這個buf的資源。因為可以自己手動控制協程的切換。
如果是多線程的話。可能就要對這個buf加鎖。競争這一個全局資源來處理資料。這也是多線程程式設計被诟病的一個地方,需要加鎖。

用戶端直接調用函數接口。到服務端請求結果
最後需要序列化的東西如下是類_Cao_action_AddActionSupplier_Req
函數的入參都是我們需要序列化的内容。注意這裡是rpc調用的一個關鍵點。
a) 先看下我們的thritf
如果下圖。發現我們的函數入參也是打上了tag标志的。作用跟我們在結構體中打tag标志是一樣的。為了辨別一個字段的含義。
序列化的時候把這些tag序列化進去。 然後反序列化的時候靠這些tag來解析
b ) 先把圖貼出來。按着圖來講更清晰些
c) 首先我們會建立一個CByteStream的類來。序列化内容。在CByteStream的構造函數會自動寫入一個位元組的序列化標頭。值為1
<code>CByteStream(</code><code>char</code><code>* pStreamBuf = NULL, uint32_t nBufLen = 0,</code><code>bool</code> <code>bStore=</code><code>true</code><code>, </code><code>bool</code> <code>bRealWrite = </code><code>true</code><code>);</code>
pStreamBuf 是序列化buf指針
pStreamBuf 是序列化的長度
bStore true表示是否需要包資料存儲下來。 false表示不需要把資料存下來
bRealWrite 表示是否支援讀寫buf
d) 接着就開始寫類_Cao_action_AddActionSupplier_Req的成員變量。其實就是函數入參。寫的時候是先協tag就是下圖中的fid。 其實就是在thrift中已經寫好的函數入參的tag值。
具體寫的過程我們先看簡單基本類型。比如strMachineKey
1)先寫tag。 strMachineKey 的tag為1. 程式裡規定tag占兩個位元組。是以函數入參可以是0xffff個。
2 ) 接着會寫4個固定的位元組。用來存儲後面緊跟着資料的值。這裡strMachineKey的長度是512000.
3 ) 寫内容 。 把strMachineKey的内容寫入緊跟着的buf
針對整形和長整形就不說了
大同小異
e) 接着我們關注下 是怎麼寫結構體oActionInfo的。
1)先寫tag。 oActionInfo 的tag為5. 程式裡規定tag占兩個位元組。
2 ) 接着針對結構體這裡 會寫4個固定的位元組用來存結構體序列化長度。因為開始不知道是以值為0。
3 ) 接着寫字段DistributorId。 它在oActionInfo結構體中的tag值為6.類型為int64. 是以先寫tag=6占兩個位元組,接着配置設定4個位元組存長度。最後配置設定8個位元組存内容
4)跟着寫DisShopId字段。就不細說了
5)最後寫了2個位元組包尾
6)最後 回寫結構體的長度
這裡注意下寫結構體時候的寫法。不注意的話會看錯。
1)這裡先拿到開始寫結構體的buf指針。注意這裡是用的int32_t。占四個位元組。跟前面保持一直。這裡用來的存後面總序列虛化結構體提的總長度。
2)由于剛開始的時候 并不知道後面的結構體會序列化多少個位元組。是以這裡先寫4個位元組。
同時把這便宜的4個位元組的記憶體值 設定為0
bs<<0; (這裡其實建議寫成 bs<<int32_t(0) 會好一點。開起來一緻)
這裡開始沒注意。以為寫4個位元組值為0的 結構體的頭。其實這裡是放結構體長度的
3)最後第5步。 重新指派 結構體的長度
f)最後對整個_Cao_action_AddActionSupplier_Req寫了兩個位元組的包尾
g) 我麼可以看到oActionInfo其實有一堆的字段。但是我們在請求的時候隻寫了兩個字段。所有在序列化的時候也隻序列化了兩個字段
其實我們可以看到我們的這種序列化,很整齊。很規則。比較緊湊。但是并不節省空間。這個裡面有很多資料可以壓縮的。但是壓縮帶來一個問題就是解壓的時候很消耗cpu的。跟我的業務場景不服和。也沒必要。
其實知道了資料是怎麼寫入的 解析起來就很容易了。其實這種序列化就是兩邊約定規則。知道規則以後就可以解析了
解析的具體步驟就不詳細說了。這裡說下解析的時候幾個特殊的地方
1、因為tag占2個位元組。是以函數入參不能大于0xffff. 一個結構體的字段個數不能大于0xffff
2、假如前端傳入的tag在解析端找不到。解析端會偏移處理下一個tag。是以這是為什麼我們可以删除字段的原因。
比如前端傳入的結構如下
struct A{
1:int aa
2:int bb
}
但是服務端背景編譯後删除了一個字段
1:int aa
a)如果前端隻填了字段aa。 那麼解析起來沒有任何問題.因為不會把字段bb的任何資訊序列化進去。
b)假如前端填了 aa 和 bb字段。
那麼服務端在解析的時候。拿到tag2。發現找不到對應的資料。
那麼它會偏移4個位元組取tag2對應字段内容所占的位元組數。比如這裡是4.
接着它發現是4.就偏移4個位元組。不處理字段值内容。直接取下一個tag進行處理
這也就是我們為什麼能删除字段的原因。
這樣看來我們的函數入參其實也是可以删除的
3、我們服務端新增字段重新編譯。前端沒有對應的tag。根本不會序列化進來。這也是我們可以增加字段的原因。
4、解析的時候如果發現tag為0.則會是認為解析結束。是以我們的tag是不能為0的
5、這樣我們也就能為服務端函數增加入參的。 同一個函數比如前端的入參是4個。服務端可以增加N個. 但是注意不能占用 函數已經用的tag。否則會有問題。而且為了保證函數的統一性。最好别這麼做。
6、到這裡已經很清晰了。 最後再說一次不能改tag對應的類型。
我們的這一套就是遠端調用rpc服務。通過我們的序列化。
其實就能了解所謂的RPC服務是什麼樣的。
說白了,遠端調用就是将對象名、函數名、參數等傳遞給遠端伺服器,伺服器将處理結果傳回給用戶端。
為了能解析出這些資訊。在入參的時候做上辨別(這裡是打tag).
谷歌的protobuf也用過。跟thrift其實差不多但是序列化和反序列的話的具體實作是有些不同的。
谷歌的protobuf更節省空間
以前具體看過序列化的源碼。覺得序列化反序列化以及rpc很神秘。現在看了源碼才發現确實寫的确實好,
但是沒那麼神秘裡。其實就是按一定規則組包。是以還是要多看源碼啊。
我們用的thrift就是 facebook的thrift。但是改了些東西。大體是一樣的。