序列化:數(shù)據(jù)跨越邊界的翻譯官
序列化(Serialization)用于描述RPC服務(wù)接口和數(shù)據(jù)結(jié)構(gòu)。在RPC通信中,客戶端和服務(wù)器之間傳輸?shù)臄?shù)據(jù)通常是結(jié)構(gòu)化的,如調(diào)用方法、請求參數(shù)、返回值等。這些結(jié)構(gòu)化數(shù)據(jù)需要通過序列化過程轉(zhuǎn)換為二進制流,以便在網(wǎng)絡(luò)中進行傳輸。
目前,常見的跨語言序列化編碼方式包括XML、JSON和Protobuf。盡管XML曾經(jīng)廣泛使用,但現(xiàn)在已經(jīng)逐漸被淘汰。JSON目前正處于其使用高峰,而Protobuf則是一種新興并且正在快速發(fā)展的序列化方式。值得一提的是,gRPC默認選擇使用Protobuf作為其序列化方式。
JSON
JSON(JavaScript Object Notation)是一種輕量級的文本數(shù)據(jù)格式,以其優(yōu)秀的可讀性、靈活性和跨語言兼容性而廣受歡迎。由于其結(jié)構(gòu)簡單、規(guī)范明確,JSON在Web開發(fā)、移動應(yīng)用、API通信等領(lǐng)域得到了廣泛應(yīng)用。同時,JSON還可以與其他技術(shù)和工具集成,如RESTful API、NoSQL數(shù)據(jù)庫等,進一步擴展了其應(yīng)用范圍。
type Message struct {
Int int32 `json:"int"`
Str string `json:"str"`
Bool bool `json:"bool"`
}
message := Message{}
message.Int = 12345
message.Str = "hello"
message.Bool = true
marshal, _ := json.Marshal(&message)
fmt.Println(fmt.Sprintf("JSON:%s ", string(marshal)))
fmt.Println(fmt.Sprintf("長度:%d 字節(jié) ", len(marshal)))
fmt.Println(fmt.Sprintf("二進制流:%08b", marshal))
JSON:{"int":12345,"str":"hello","bool":true}
長度:39 字節(jié)
二進制流:[01111011 00100010 01001001 01101110 01110100 00100010 00111010 00110001 00110010 00110011 00110100 00110101 00101100 00100010 01010011 01110100 01110010 00100010 00111010 00100010 01101000 011 01101100 01101100 01101111 00100010 00101100 00100010 01000010 01101111 01101111 01101100 00100010 00111010 01110100 01110010 01110101 01100101 01111101]
假設(shè)用UTF-8編碼,每個字符占用1個字節(jié)。估算上面JSON占有的內(nèi)存數(shù)據(jù)。
1)字段名占用的內(nèi)存空間: int (3字節(jié))+ str (3字節(jié))+ str (4字節(jié))= 10字節(jié)。
2)字段值占用的內(nèi)存空間:12345 (5字節(jié))+ hello (5字節(jié))+ true (4字節(jié))= 14字節(jié)。需注意JSON中數(shù)值和布爾類型會被編碼為文本字符串。
3)分隔符和其他符號占用的內(nèi)存空間::(3字節(jié))+ ,(2字節(jié))+ {}(2字節(jié))+ "(8字節(jié))= 15字節(jié)
JSON的內(nèi)存占用為:10 + 14 + 15 = 39個字節(jié),其中有效的字段值只占14個字節(jié)。 可見JSON的內(nèi)存占有比較大且效率低,這個問題的主要有如下原因。
1)非字符串編碼低效:int 字段值,轉(zhuǎn)成 JSON 要五個字節(jié)。 bool 字段值占了四個字節(jié)。
2)字段名信息冗余:同一個對像,只是字段值不同,每次都要傳輸相同的字段名。
Protobuf
Protobuf(Protocol Buffers)是由Google開發(fā)的一種高效的二進制序列化格式。它設(shè)計精巧,旨在提供一種簡單、動態(tài)、可擴展且性能高效的數(shù)據(jù)序列化方案。相比于XML和JSON等其他序列化編碼方式,Protobuf具有更小的數(shù)據(jù)體積和更快的數(shù)據(jù)解析速度,這使得它在處理大量數(shù)據(jù)和高性能需求的場景中具有顯著優(yōu)勢。
message Message {
int32 int = 1;
string str = 2;
bool bool = 3;
}
message := pb.Message{}
message.Int = 12345
message.Str = "hello"
message.Bool = true
marshal, _ := proto.Marshal(&message)
fmt.Println(fmt.Sprintf("長度:%d 字節(jié) ", len(marshal)))
fmt.Println(fmt.Sprintf("二進制流:%08b", marshal))
長度:12 字節(jié)
二進制流:[00001000 10111001 01100000 00010010 00000101 01101000 01100101 01101100 01101100 01101111 00011000 00000001]
Varint
Varint是一種變長的整數(shù)類型,相較于定長的編碼方式,更能節(jié)省空間。Varint使用每個字節(jié)的最高位(Most Significant Bit,MSB),記錄字節(jié)讀取是否結(jié)束。 如果MSB 為1 ,表示還有后序字節(jié),一直讀到 MSB 為 0 的字節(jié)為止。一個int32整型通常占據(jù)4個字節(jié)也就是32位,但使用Varint編碼只需1個字節(jié)。
0000 0000 | 0000 0000 | 0000 0000 | 0000 0001
0000 0001
wire type
Protobuf將每個字段編碼后從邏輯上分為三個部分。
<tag> <type> [<length>] <data>
其中tag 里面會包含兩部分信息:字段序號(field number),字段類型(wire type)。tag,type和 length 都用 VarInts 表示。
Protobuf 在 3 版本中定義了 4 種類型 。
0 VarInt 表示int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 64-bit 表示fixed64, sfixed64, double
2 Length-delimited 表示 string, bytes, embedded messages, repeated 字段
5 32-bit 表示fixed32, sfixed32, float
由于3 和 4 表示的類型已經(jīng)廢棄,類型比較少,所以Protobuf 在編碼時候只用了 3 bit,實際傳輸以 (tag<<3)|type 的方式傳輸。

使用 tag 的優(yōu)點是不用重復(fù)傳輸字段名,但也因為沒有字段名,所以須維護字段名和 tag 的映射關(guān)系。這個映射關(guān)系由.proto維護 。
將message通過Protobuf序列化的二進制串,與原始字段名和字段值有如下的對應(yīng)關(guān)系。

Protobuf在多個方面都展現(xiàn)出與JSON相比的優(yōu)勢。首先,Protobuf的數(shù)據(jù)更為緊湊,相較于JSON的文本格式,它可以大幅減少數(shù)據(jù)的存儲和傳輸開銷。其次,Protobuf的處理速度更快,由于采用了二進制編碼,它能夠更快地將二進制數(shù)據(jù)轉(zhuǎn)換為內(nèi)存對象。此外,Protobuf還提供了類型安全的保障,通過預(yù)先定義消息結(jié)構(gòu),確保數(shù)據(jù)的一致性和正確性。
然而,與JSON相比,Protobuf由于采用了二進制編碼,Protobuf的數(shù)據(jù)在可讀性方面稍遜一籌。此外,Protobuf需要預(yù)先定義消息結(jié)構(gòu),這增加了一些額外的工作量,并且在消息結(jié)構(gòu)發(fā)生變化時,需要同步進行更新。
需要明確的是,序列化并非RPC協(xié)議本身,而是將RPC傳輸?shù)慕Y(jié)構(gòu)化數(shù)據(jù)(如請求參數(shù)、返回值)序列化成二進制流的過程。因此,RPC協(xié)議中需要包含序列化標識,以便接收端根據(jù)序列化標識將二進制流反序列化成結(jié)構(gòu)化數(shù)據(jù)。然而,像HTTP/1協(xié)議直接將文本數(shù)據(jù)轉(zhuǎn)換成二進制流,因此不需要額外的序列化標識。
序列化的性能直接影響到RPC協(xié)議的性能。一個優(yōu)秀的序列化編碼方式應(yīng)該在占用更低的內(nèi)存空間的同時,保持更高的編解碼效率。除了JSON和Protobuf之外,還有一些特定語言的序列化編碼方式,如Java的Hessian、Kryo等,它們在特定的場景中也可以作為優(yōu)秀的選擇。
總結(jié):沒有銀彈,只有最合適的選擇
構(gòu)建高效、健壯的服務(wù)通信體系,其核心在于制定一套能夠有效協(xié)調(diào)跨服務(wù)、跨邊界協(xié)作的規(guī)范與機制。在復(fù)雜的異構(gòu)系統(tǒng)交互中,必須系統(tǒng)性地解決數(shù)據(jù)格式的統(tǒng)一性、信息傳輸?shù)母咝砸约胺椒ǘx的明確性這三大基礎(chǔ)問題。
標準化框架(如gRPC): 它們通過整合HTTP/2的流式交互能力和ProtoBuf的統(tǒng)一編解碼方案,構(gòu)建了一個功能完備、開箱即用且具有廣泛生態(tài)支持的開放RPC體系。這尤其適用于需要跨語言、跨團隊協(xié)作以及面臨復(fù)雜多變公網(wǎng)環(huán)境的場景。
精簡的自研協(xié)議: 它們更聚焦于榨取內(nèi)網(wǎng)環(huán)境下的極致性能潛力。通過高度定制、可擴展的報文結(jié)構(gòu)設(shè)計和靈活的過程控制,自研協(xié)議能夠針對特定業(yè)務(wù)場景和硬件環(huán)境進行深度優(yōu)化,滿足對低延遲、高吞吐的嚴苛要求。
這種“公網(wǎng)標準化”與“內(nèi)網(wǎng)優(yōu)化”并存的雙軌實踐,深刻體現(xiàn)了系統(tǒng)設(shè)計中的一個根本邏輯:在標準化帶來的互操作性、生態(tài)繁榮與特定場景下的極致優(yōu)化之間,尋求一種動態(tài)的、彈性的平衡。 技術(shù)決策的目標應(yīng)是既能有力支撐當前業(yè)務(wù)的快速發(fā)展,又能為未來通信潛能的持續(xù)釋放奠定堅實基礎(chǔ)。
轉(zhuǎn)自https://www.cnblogs.com/poemyang/p/19073206
該文章在 2025/9/6 16:30:15 編輯過