偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Thrift 序列化協(xié)議淺析

原創(chuàng) 精選
開(kāi)源
Thrift 是 Facebook 開(kāi)源的一個(gè)高性能,輕量級(jí) RPC 服務(wù)框架,是一套全棧式的 RPC 解決方案,包含序列化與服務(wù)通信能力,并支持跨平臺(tái)/跨語(yǔ)言。

作者| 楊晨曦

背景

Thrift 是 Facebook 開(kāi)源的一個(gè)高性能,輕量級(jí) RPC 服務(wù)框架,是一套全棧式的 RPC 解決方案,包含序列化與服務(wù)通信能力,并支持跨平臺(tái)/跨語(yǔ)言。整體架構(gòu)如圖所示:

圖片

Thrift 軟件棧定義清晰,各層的組件松耦合、可插拔,能夠根據(jù)業(yè)務(wù)場(chǎng)景靈活組合,如圖所示:

圖片

Thrift 本身是一個(gè)比較大的話題,這篇文章不會(huì)涉及到全部?jī)?nèi)容,只會(huì)涉及到其中的序列化協(xié)議。

圖片

協(xié)議原理

Binary 協(xié)議

消息格式

這里通過(guò)一個(gè)示例對(duì) Binary 消息格式進(jìn)行直觀的展示,IDL 定義如下:

// 接口
service SupService {
SearchDepartmentByKeywordResponse SearchDepartmentByKeyword(
1: SearchDepartmentByKeywordRequest request)
}

// 請(qǐng)求
struct SearchDepartmentByKeywordRequest {
1: optional string Keyword
2: optional i32 Limit
3: optional i32 Offset
}

// 假設(shè)request的payload如下:
{
Keyword: "lark",
Limit: 50,
Offset: nil,
}
編碼簡(jiǎn)圖

圖片

編碼具體內(nèi)容
抓包拿到編碼后的字節(jié)流(轉(zhuǎn)成了十進(jìn)制,方便大家看)

/* 接口名長(zhǎng)度 */ 0 0 0 25
/* 接口名 */ 83 101 97 114 99 104 68 101 112 97 114 116
109 101 110 116 66 121 75 101 121 119 111
114 100
/* 消息類(lèi)型 */ 1
/* 消息序號(hào) */ 0 0 0 1
/* keyword 字段類(lèi)型 */ 11
/* keyword 字段ID*/ 0 1
/* keyword len */ 0 0 0 4
/* keyword value */ 108 97 114 107
/* limit 字段類(lèi)型 */ 8
/* limit 字段ID*/ 0 2
/* limit value */ 0 0 0 50
/* 字段終止符 */ 0
編碼含義

消息頭

  • msg_type:消息類(lèi)型,包含四種類(lèi)型
  • Call:客戶(hù)端消息。調(diào)用遠(yuǎn)程方法,并且期待對(duì)方發(fā)送響應(yīng)。
  • OneWay:客戶(hù)端消息。調(diào)用遠(yuǎn)程方法,不期待響應(yīng)。
  • Reply:服務(wù)端消息。正常響應(yīng)。
  • Exception:服務(wù)端消息。異常響應(yīng)。
  • msg_seq_id:消息序號(hào)??蛻?hù)端使用消息序號(hào)來(lái)處理響應(yīng)的失序到達(dá),實(shí)現(xiàn)請(qǐng)求和響應(yīng)的匹配。服務(wù)端不需要檢查該序列號(hào),也不能對(duì)序列號(hào)有任何的邏輯依賴(lài),只需要響應(yīng)的時(shí)候?qū)⑵湓瓨臃祷丶纯伞?/li>

消息體

消息體分為兩種編碼模式:

  1. 定長(zhǎng)類(lèi)型 -> T-V 模式,即:字段類(lèi)型 + 序號(hào) + 字段值
  2. 變長(zhǎng)類(lèi)型 -> T-L-V 模式,即:字段類(lèi)型 + 序號(hào) + 字段長(zhǎng)度 + 字段值
  • field_type:字段類(lèi)型,包括 String、I64、Struct、Stop 等。字段類(lèi)型有兩個(gè)作用:
  • Stop 類(lèi)型用于停止嵌套解析
  • 非 Stop 類(lèi)型用于 Skip(Skip 操作是跳過(guò)當(dāng)前字段,會(huì)在「常見(jiàn)問(wèn)題 - 兼容性」進(jìn)行講解)
  • fied_id:字段序號(hào),解碼時(shí)通過(guò)序號(hào)確定字段
  • len:字段長(zhǎng)度,用于變長(zhǎng)類(lèi)型,如 String
  • value:字段值

數(shù)據(jù)格式

1. 定長(zhǎng)數(shù)據(jù)類(lèi)型

數(shù)據(jù)類(lèi)型

類(lèi)型標(biāo)識(shí)(8 位)

類(lèi)型尺寸(單位:字節(jié))

bool

2

1

byte

3

1

double

4

8

i16

6

2

i32

8

4

i64

10

8

2. 變長(zhǎng)數(shù)據(jù)類(lèi)型

數(shù)據(jù)類(lèi)型

類(lèi)型標(biāo)識(shí)(8 位)

類(lèi)型尺寸(長(zhǎng)度 + 值)

string

11

4 + N

struct

12

嵌套數(shù)據(jù) + 一個(gè)字節(jié)停止符(0)

map

13

1 + 1 + 4 + N*(X+Y) 【key 類(lèi)型 + val 類(lèi)型 + 長(zhǎng)度 + 值】

set

14

1 + 4 + N 【val 類(lèi)型 + 長(zhǎng)度 + 值】

list

15

1 + 4 + N 【val 類(lèi)型 + 長(zhǎng)度 + 值】

其他協(xié)議

Compact 協(xié)議

Compact 協(xié)議是二進(jìn)制壓縮協(xié)議,在大部分字段的編碼方式上與 Binary 協(xié)議保持一致。區(qū)別在于整數(shù)類(lèi)型(包括變長(zhǎng)類(lèi)型的長(zhǎng)度)采用了【先 zigzag 編碼 ,再 varint 壓縮編碼】實(shí)現(xiàn),最大化節(jié)省空間開(kāi)銷(xiāo)。

那么問(wèn)題來(lái)了,varint 和 zigzag 是什么?

varint 編碼

解決的問(wèn)題:定長(zhǎng)存儲(chǔ)的整數(shù)類(lèi)型絕對(duì)值較小時(shí)空間浪費(fèi)大

據(jù)統(tǒng)計(jì),RPC 通信時(shí)大部分時(shí)候傳遞的整數(shù)值都很小,如果使用定長(zhǎng)存儲(chǔ)會(huì)很浪費(fèi)。

舉個(gè) ??,對(duì) i32 類(lèi)型的 7 進(jìn)行編碼,可以說(shuō)前面 3 個(gè)字節(jié)都浪費(fèi)了:

00000000 00000000 00000000 00000111

解決思路:將整數(shù)類(lèi)型由定長(zhǎng)存儲(chǔ)轉(zhuǎn)為變長(zhǎng)存儲(chǔ)(能用 1 個(gè)字節(jié)存下就堅(jiān)決不用 2 個(gè)字節(jié))

原理并不復(fù)雜,就是將整數(shù)按 7bit 分段,每個(gè)字節(jié)的最高位作為標(biāo)識(shí)位,標(biāo)識(shí)后一個(gè)字節(jié)是否屬于該數(shù)據(jù)。1 代表后面的字節(jié)還是屬于當(dāng)前數(shù)據(jù),0 代表這是當(dāng)前數(shù)據(jù)的最后一個(gè)字節(jié)。

以 i32 類(lèi)型,數(shù)值 955 為例,可以看出,由原來(lái)的 4 字節(jié)壓縮到了 2 字節(jié):

binary編碼:       00000000  00000000  00000011  10111011
切分: 0000 0000000 0000000 0000111 0111011
compact編碼: 00000111 10111011

當(dāng)然,varint 編碼同樣存在缺陷,那就是存儲(chǔ)大數(shù)的時(shí)候,反而會(huì)比 binary 的空間開(kāi)銷(xiāo)更大:本來(lái) 4 個(gè)字節(jié)存下的數(shù)可能需要 5 個(gè)字節(jié),8 個(gè)字節(jié)存下的數(shù)可能需要 10 個(gè)字節(jié)。

zigzag 編碼

解決的問(wèn)題:絕對(duì)值較小的負(fù)數(shù)經(jīng)過(guò) varint 編碼后空間開(kāi)銷(xiāo)較大 舉個(gè) ??,i32 類(lèi)型的負(fù)數(shù)(-11)

原碼:         10000000  00000000  00000000  00001011
反碼: 11111111 11111111 11111111 11110100
補(bǔ)碼: 11111111 11111111 11111111 11110101
varint編碼: 00001111 11111111 11111111 11111111 11110101

顯然,對(duì)于絕對(duì)值較小的負(fù)數(shù),用 varint 編碼以后前導(dǎo) 1 過(guò)多,難以壓縮,空間開(kāi)銷(xiāo)比 binary 編碼還大。

解決思路:負(fù)數(shù)轉(zhuǎn)正數(shù),從而把前導(dǎo) 1 轉(zhuǎn)成前導(dǎo) 0,便于 varint 壓縮

算法公式 & 步驟 & 示范:

// 算法公式
32位: (n << 1) ^ (n >> 31)
64位: (n << 1) ^ (n >> 63)


/*
* 算法步驟:
* 1. 不分正負(fù):符號(hào)位后置,數(shù)值位前移
* 2. 對(duì)于負(fù)數(shù):符號(hào)位不變,數(shù)值位取反
*/


// 示例
負(fù)數(shù)(-11)
補(bǔ)碼: 11111111 11111111 11111111 11110101
符號(hào)位后置,數(shù)值位前移: 11111111 11111111 11111111 11101011
符號(hào)位不變,數(shù)值位取反(21): 00000000 00000000 00000000 00010101

正數(shù)(11)
補(bǔ)碼: 00000000 00000000 00000000 00010101
符號(hào)位后置,數(shù)值位前移(22): 00000000 00000000 00000000 00101010

【奇怪的知識(shí)】為什么取名叫 zigzag?

因?yàn)檫@個(gè)算法將負(fù)數(shù)編碼成正奇數(shù),正數(shù)編碼成偶數(shù)。最后效果是正負(fù)數(shù)穿插向前,就像這樣:

編碼前       編碼后
0 0
-1 1
1 2
-2 3
2 4

Json 協(xié)議

Thrift 不僅支持二進(jìn)制序列化協(xié)議,也支持 Json 這種文本協(xié)議

數(shù)據(jù)格式

/* bool、i8、i16、i32、i64、double、string */
"編號(hào)": {
"類(lèi)型": "值"
}
// 示例
"1": {
"str": "keyword"
}


/* struct */
"編號(hào)": {
"rec": {
"成員編號(hào)": {
"成員類(lèi)型": "成員值"
},
...
}
}
// 示例
"1": {
"rec": {
"1": {
"i32": 50
}
}
}


/* map */
"編號(hào)": {
"map": [
"鍵類(lèi)型",
"值類(lèi)型",
元素個(gè)數(shù),
"鍵1",
"值1",
...
"鍵n",
"值n"
]
}
// 示例
"6": {
"map": [
"i64",
"str",
1,
666,
"mapValue"
]
}


/* List */
"編號(hào)": {
"set/lst": [
"值類(lèi)型",
元素個(gè)數(shù),
"ele1",
"ele2",
"elen"
]
}
// 示例
"2": {
"lst": [
"str",
2,
"lark","keyword"]
}

case 分析

修改字段類(lèi)型導(dǎo)致 RPC 超時(shí)

現(xiàn)象:A 服務(wù)訪問(wèn) B 服務(wù),業(yè)務(wù)邏輯短時(shí)間處理完,但整個(gè)請(qǐng)求 15s 超時(shí),必現(xiàn)。

直接原因:IDL 類(lèi)型被修改;并且只升級(jí)了服務(wù)端(B 服務(wù)),沒(méi)升級(jí)客戶(hù)端(A 服務(wù))

圖片

本質(zhì)原因:string 是變長(zhǎng)編碼,i64 是定長(zhǎng)編碼。由于客戶(hù)端沒(méi)有升級(jí),所以反序列化的時(shí)候,會(huì)把 signTime 當(dāng)做 string 類(lèi)型來(lái)解析。而變長(zhǎng)編碼是 T-L-V 模式,所以解析的時(shí)候會(huì)把 signTime 的低位 4 字節(jié)翻譯成 string 的 length。

signTime 是時(shí)間戳,大整數(shù),比如:1624206147902,轉(zhuǎn)成二進(jìn)制為:

00000000 00000000 00000001 01111010 00101010 00111011 00000001 00111110

低位 4 字節(jié)轉(zhuǎn)成十進(jìn)制為:378

也就是要再讀 378 個(gè)字節(jié)作為 SignTime 的值,這已經(jīng)超過(guò)了整個(gè) payload 的大小,最終導(dǎo)致 Socket 讀超時(shí)。

【注】修改類(lèi)型不一定就會(huì)導(dǎo)致超時(shí),如果 value 的值比較小,解析到的 length 也比較小,能夠保證讀完。但是錯(cuò)誤的解析可能會(huì)導(dǎo)致各種預(yù)期之外的情況,包括:

  1. 亂碼
  2. 空值
  3. 報(bào)錯(cuò):unknown data type xxx (skip 異常)

常見(jiàn)問(wèn)題

兼容性

增加字段

通過(guò) skip 來(lái)跳過(guò)增加的字段,從而保證兼容性

圖片

刪除字段

編譯生成的解析代碼是基于 field_id 的 switch-case 結(jié)構(gòu),語(yǔ)法結(jié)構(gòu)上直接具備兼容性。

圖片

修改字段名

不破壞兼容性,因?yàn)?binary 協(xié)議不會(huì)對(duì) name 進(jìn)行編碼

Exception

Thrift 有兩種 Exception,一種是框架內(nèi)置的異常,一種是 IDL 自定義的異常。

框架內(nèi)置的異常包括:「方法名錯(cuò)誤」、「消息序列號(hào)錯(cuò)誤」、「協(xié)議錯(cuò)誤」,這些異常由框架捕獲并封裝成 Exception 消息,反序列化時(shí)會(huì)轉(zhuǎn)成 error 并拋給上層,邏輯如下:

圖片

另一種異常是由用戶(hù)在 IDL 中自定義的,關(guān)鍵字是 exception,用法上跟 struct 沒(méi)有太大區(qū)別。

圖片

optional、require 實(shí)現(xiàn)原理

optional 表示字段可填,require 表示必填

字段被標(biāo)識(shí)為 optional 之后:

  • 基本類(lèi)型會(huì)被編譯為指針類(lèi)型
  • 序列化代碼會(huì)做空值判斷,如果字段為空,則不會(huì)被編碼

字段被標(biāo)識(shí)為 require 之后:

  • 基本類(lèi)型會(huì)被編譯為非指針類(lèi)型(復(fù)合類(lèi)型 optional 和 require 沒(méi)區(qū)別)
  • 序列化不會(huì)做空值判斷,字段一定會(huì)被編碼。如果沒(méi)有顯式賦值,就編碼默認(rèn)值(默認(rèn)空值,或者 IDL 顯式指定的默認(rèn)值)?
責(zé)任編輯:未麗燕 來(lái)源: 字節(jié)跳動(dòng)技術(shù)團(tuán)隊(duì)
相關(guān)推薦

2009-09-09 14:45:41

XML序列化和反序列化

2009-09-09 16:53:49

C# XmlSeria序列化

2009-09-09 15:54:48

C# XML序列化

2009-08-25 14:59:39

C# XML序列化應(yīng)用

2009-09-09 17:14:24

XML序列化

2009-08-25 15:15:08

C#對(duì)象序列化應(yīng)用

2022-08-06 08:41:18

序列化反序列化Hessian

2011-06-01 15:05:02

序列化反序列化

2009-08-24 17:14:08

C#序列化

2011-05-18 15:20:13

XML

2023-12-13 13:49:52

Python序列化模塊

2018-03-19 10:20:23

Java序列化反序列化

2009-08-06 11:16:25

C#序列化和反序列化

2013-03-11 13:55:03

JavaJSON

2009-06-14 22:01:27

Java對(duì)象序列化反序列化

2019-11-20 10:07:23

web安全PHP序列化反序列化

2009-08-25 14:24:36

C#序列化和反序列化

2011-06-01 14:50:48

2011-06-01 14:26:11

序列化

2021-11-18 07:39:41

Json 序列化Vue
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)