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

使用 tag 的優(yōu)點(diǎn)是不用重復(fù)傳輸字段名,但也因?yàn)闆](méi)有字段名,所以須維護(hù)字段名和 tag 的映射關(guān)系。這個(gè)映射關(guān)系由.proto維護(hù) 。
將message通過(guò)Protobuf序列化的二進(jìn)制串,與原始字段名和字段值有如下的對(duì)應(yīng)關(guān)系。

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