深入了解gRPC:協(xié)議

gRPC 是基于 HTTP/2 協(xié)議的捷雕,要深刻理解 gRPC衔峰,理解下 HTTP/2 是必要的悼瓮。本篇文章會(huì)先簡單介紹一下 HTTP/2 相關(guān)的知識(shí)徊哑,然后再介紹下 gRPC 是如何基于 HTTP/2 構(gòu)建的笛钝。

HTTP/1.x

HTTP 協(xié)議可以算是現(xiàn)階段 Web 上面最通用的協(xié)議了质况,在之前很長一段時(shí)間愕宋,很多應(yīng)用都是基于 HTTP/1.x 協(xié)議,HTTP/1.x 協(xié)議是一個(gè)文本協(xié)議结榄,可讀性非常好中贝,但其實(shí)并不高效,筆者主要碰到過幾個(gè)問題:

parser

如果要解析一個(gè)完整的 HTTP 請求臼朗,首先我們需要能正確的讀出 HTTP header邻寿。HTTP header 各個(gè) fields 使用\r\n 分隔,然后跟 body 之間使用\r\n\r\n分隔视哑。解析完 header 之后绣否,我們才能從 header 里面的 content-length 拿到 body 的 size,從而讀取 body挡毅。

這套流程其實(shí)并不高效蒜撮,因?yàn)槲覀冃枰x取多次,才能將一個(gè)完整的 HTTP 請求給解析出來跪呈,雖然在代碼實(shí)現(xiàn)上面段磨,有很多優(yōu)化方式,譬如:

  • 一次將一大塊數(shù)據(jù)讀取到 buffer 里面避免多次 IO read
  • 讀取的時(shí)候直接匹配 \r\n 的方式流式解析

但上面的方式對于高性能服務(wù)來說耗绿,終歸還是會(huì)有開銷苹支。其實(shí)最主要的問題在于,HTTP/1.x 的協(xié)議是 文本協(xié)議缭乘,是給人看的沐序,對機(jī)器不友好琉用,如果要對機(jī)器友好堕绩,二進(jìn)制協(xié)議才是更好的選擇。

如果大家對解析 HTTP/1.x 很感興趣邑时,可以研究下 http-parser奴紧,一個(gè)非常高效小巧的 C library,見過不少框架都是集成了這個(gè)庫來處理 HTTP/1.x 的晶丘。

Request/Response

HTTP/1.x 另一個(gè)問題就在于它的交互模式黍氮,一個(gè)連接每次只能一問一答,也就是 client 發(fā)送了 request 之后浅浮,必須等到 response沫浆,才能繼續(xù)發(fā)送下一次請求。

這套機(jī)制是非常簡單滚秩,但會(huì)造成網(wǎng)絡(luò)連接利用率不高专执。如果需要同時(shí)進(jìn)行大量的交互,client 需要跟 server 建立多條連接郁油,但連接的建立也是有開銷的本股,所以為了性能攀痊,通常這些連接都是長連接一直保活的拄显,雖然對于 server 來說同時(shí)處理百萬連接也沒啥太大的挑戰(zhàn)苟径,但終歸效率不高。

Push

用 HTTP/1.x 做過推送的同學(xué)躬审,大概就知道有多么的痛苦棘街,因?yàn)?HTTP/1.x 并沒有推送機(jī)制。所以通常兩種做法:

  • Long polling 方式盒件,也就是直接給 server 掛一個(gè)連接蹬碧,等待一段時(shí)間(譬如 1 分鐘),如果 server 有返回或者超時(shí)炒刁,則再次重新 poll恩沽。
  • Web-socket,通過 upgrade 機(jī)制顯式的將這條 HTTP 連接變成裸的 TCP翔始,進(jìn)行雙向交互罗心。

相比 Long polling,筆者還是更喜歡 web-socket 一點(diǎn)城瞎,畢竟更加高效渤闷,只是 web-socket 后面的交互并不是傳統(tǒng)意義上面的 HTTP 了。

Hello HTTP/2

雖然 HTTP/1.x 協(xié)議可能仍然是當(dāng)今互聯(lián)網(wǎng)運(yùn)用最廣泛的協(xié)議脖镀,但隨著 Web 服務(wù)規(guī)模的不斷擴(kuò)大飒箭,HTTP/1.x 越發(fā)顯得捉緊見拙,我們急需另一套更好的協(xié)議來構(gòu)建我們的服務(wù),于是就有了 HTTP/2蜒灰。

HTTP/2 是一個(gè)二進(jìn)制協(xié)議弦蹂,這也就意味著它的可讀性幾乎為 0,但幸運(yùn)的是强窖,我們還是有很多工具凸椿,譬如 Wireshark, 能夠?qū)⑵浣馕龀鰜怼?/p>

在了解 HTTP/2 之前翅溺,需要知道一些通用術(shù)語:

  • Stream: 一個(gè)雙向流脑漫,一條連接可以有多個(gè) streams。
  • Message: 也就是邏輯上面的 request咙崎,response优幸。
  • Frame::數(shù)據(jù)傳輸?shù)淖钚挝弧C總€(gè) Frame 都屬于一個(gè)特定的 stream 或者整個(gè)連接褪猛。一個(gè) message 可能有多個(gè) frame 組成网杆。

Frame Format

Frame 是 HTTP/2 里面最小的數(shù)據(jù)傳輸單位,一個(gè) Frame 定義如下:

http2-grpc.jpg
  • Length:也就是 Frame 的長度,默認(rèn)最大長度是 16KB跛璧,如果要發(fā)送更大的 Frame严里,需要顯式的設(shè)置 max frame size。
  • Type:Frame 的類型追城,譬如有 DATA刹碾,HEADERS,PRIORITY 等座柱。
  • Flag 和 R:保留位迷帜,可以先不管。
  • Stream Identifier:標(biāo)識(shí)所屬的 stream色洞,如果為 0戏锹,則表示這個(gè) frame 屬于整條連接。
  • Frame Payload:根據(jù)不同 Type 有不同的格式火诸。
    可以看到锦针,F(xiàn)rame 的格式定義還是非常的簡單,按照官方協(xié)議置蜀,可以非常方便的寫一個(gè)出來奈搜。

Multiplexing

HTTP/2 通過 stream 支持了連接的多路復(fù)用,提高了連接的利用率盯荤。Stream 有很多重要特性:

  • 一條連接可以包含多個(gè) streams馋吗,多個(gè) streams 發(fā)送的數(shù)據(jù)互相不影響。
  • Stream 可以被 client 和 server 單方面或者共享使用秋秤。
  • Stream 可以被任意一段關(guān)閉宏粤。
  • Stream 會(huì)確定好發(fā)送 frame 的順序,另一端會(huì)按照接受到的順序來處理灼卢。
  • Stream 用一個(gè)唯一 ID 來標(biāo)識(shí)绍哎。

這里在說一下 Stream ID,如果是 client 創(chuàng)建的 stream芥玉,ID 就是奇數(shù)蛇摸,如果是 server 創(chuàng)建的备图,ID 就是偶數(shù)灿巧。ID 0x00 和 0x01 都有特定的使用場景。

Stream ID 不可能被重復(fù)使用揽涮,如果一條連接上面 ID 分配完了抠藕,client 會(huì)新建一條連接。而 server 則會(huì)給 client 發(fā)送一個(gè) GOAWAY frame 強(qiáng)制讓 client 新建一條連接蒋困。

為了更大的提高一條連接上面的 stream 并發(fā)盾似,可以考慮調(diào)大 SETTINGS_MAX_CONCURRENT_STREAMS,在 TiKV 里面,我們就遇到過這個(gè)值比較小零院,整體吞吐上不去的問題溉跃。

這里還需要注意,雖然一條連接上面能夠處理更多的請求了告抄,但一條連接遠(yuǎn)遠(yuǎn)是不夠的撰茎。一條連接通常只有一個(gè)線程來處理,所以并不能充分利用服務(wù)器多核的優(yōu)勢打洼。同時(shí)龄糊,每個(gè)請求編解碼還是有開銷的,所以用一條連接還是會(huì)出現(xiàn)瓶頸募疮。

在 TiKV 有一個(gè)版本中炫惩,我們就過分相信一條連接跑多 streams 這種方式?jīng)]有問題,就讓 client 只用一條連接跟 TiKV 交互阿浓,結(jié)果發(fā)現(xiàn)性能完全沒法用他嚷,不光處理連接的線程 CPU 跑滿,整體的性能也上不去芭毙,后來我們換成了多條連接爸舒,情況才好轉(zhuǎn)。

Priority

因?yàn)橐粭l連接允許多個(gè) streams 在上面發(fā)送 frame稿蹲,那么在一些場景下面扭勉,我們還是希望 stream 有優(yōu)先級(jí),方便對端為不同的請求分配不同的資源苛聘。譬如對于一個(gè) Web 站點(diǎn)來說涂炎,優(yōu)先加載重要的資源,而對于一些不那么重要的圖片啥的设哗,則使用低的優(yōu)先級(jí)唱捣。

我們還可以設(shè)置 Stream Dependencies,形成一棵 streams priority tree网梢。假設(shè) Stream A 是 parent震缭,Stream B 和 C 都是它的孩子,B 的 weight 是 4战虏,C 的 weight 是 12拣宰,假設(shè)現(xiàn)在 A 能分配到所有的資源,那么后面 B 能分配到的資源只有 C 的 1/3烦感。

Flow Control

HTTP/2 也支持流控巡社,如果 sender 端發(fā)送數(shù)據(jù)太快,receiver 端可能因?yàn)樘κ秩ぃ蛘邏毫μ笊胃茫蛘咧幌虢o特定的 stream 分配資源,receiver 端就可能不想處理這些數(shù)據(jù)。譬如朝群,如果 client 給 server 請求了一個(gè)視頻燕耿,但這時(shí)候用戶暫停觀看了,client 就可能告訴 server 別在發(fā)送數(shù)據(jù)了姜胖。

雖然 TCP 也有 flow control缸棵,但它僅僅只對一個(gè)連接有效果。HTTP/2 在一條連接上面會(huì)有多個(gè) streams谭期,有時(shí)候堵第,我們僅僅只想對一些 stream 進(jìn)行控制,所以 HTTP/2 單獨(dú)提供了流控機(jī)制隧出。Flow control 有如下特性:

  • Flow control 是單向的录别。Receiver 可以選擇給 stream 或者整個(gè)連接設(shè)置 window size糟港。
  • Flow control 是基于信任的陨溅。Receiver 只是會(huì)給 sender 建議它的初始連接和 stream 的 flow control window size脚祟。
  • Flow control 不可能被禁止掉。當(dāng) HTTP/2 連接建立起來之后凄诞,client 和 server 會(huì)交換 SETTINGS frames圆雁,用來設(shè)置 flow control window size。
  • Flow control 是 hop-by-hop帆谍,并不是 end-to-end 的伪朽,也就是我們可以用一個(gè)中間人來進(jìn)行 flow control。

這里需要注意汛蝙,HTTP/2 默認(rèn)的 window size 是 64 KB烈涮,實(shí)際這個(gè)值太小了,在 TiKV 里面我們直接設(shè)置成 1 GB窖剑。

HPACK

在一個(gè) HTTP 請求里面坚洽,我們通常在 header 上面攜帶很多該請求的元信息,用來描述要傳輸?shù)馁Y源以及它的相關(guān)屬性西土。在 HTTP/1.x 時(shí)代讶舰,我們采用純文本協(xié)議,并且使用\r\n來分隔需了,如果我們要傳輸?shù)脑獢?shù)據(jù)很多跳昼,就會(huì)導(dǎo)致 header 非常的龐大。另外援所,多數(shù)時(shí)候庐舟,在一條連接上面的多數(shù)請求欣除,其實(shí) header 差不了多少住拭,譬如我們第一個(gè)請求可能GET /a.txt后面緊接著是GET /b.txt兩個(gè)請求唯一的區(qū)別就是 URL path 不一樣,但我們?nèi)匀灰獙⑵渌械?fields 完全發(fā)一遍。

HTTP/2 為了結(jié)果這個(gè)問題滔岳,使用了 HPACK杠娱。雖然 HPACK 的 RFC 文檔看起來比較恐怖,但其實(shí)原理非常的簡單易懂谱煤。

HPACK 提供了一個(gè)靜態(tài)和動(dòng)態(tài)的 table摊求,靜態(tài) table 定義了通用的 HTTP header fields,譬如 method刘离,path 等室叉。發(fā)送請求的時(shí)候,只要指定 field 在靜態(tài) table 里面的索引硫惕,雙方就知道要發(fā)送的 field 是什么了茧痕。

對于動(dòng)態(tài) table,初始化為空恼除,如果兩邊交互之后踪旷,發(fā)現(xiàn)有新的 field,就添加到動(dòng)態(tài) table 上面豁辉,這樣后面的請求就可以跟靜態(tài) table 一樣令野,只需要帶上相關(guān)的 index 就可以了。

同時(shí)徽级,為了減少數(shù)據(jù)傳輸?shù)拇笮∑疲褂?Huffman 進(jìn)行編碼。這里就不再詳細(xì)說明 HPACK 和 Huffman 如何編碼了餐抢。

小結(jié)

上面只是大概列舉了一些 HTTP/2 的特性堵幽,還有一些,譬如 push弹澎,以及不同的 frame 定義等都沒有提及朴下,大家感興趣,可以自行參考 HTTP/2 RFC 文檔苦蒿。

Hello gRPC

gRPC 是 Google 基于 HTTP/2 以及 protobuf 的殴胧,要了解 gRPC 協(xié)議,只需要知道 gRPC 是如何在 HTTP/2 上面?zhèn)鬏斁涂梢粤恕?/p>

gRPC 通常有四種模式佩迟,unary团滥,client streaming,server streaming 以及 bidirectional streaming报强,對于底層 HTTP/2 來說灸姊,它們都是 stream,并且仍然是一套 request + response 模型秉溉。

Request

gRPC 的 request 通常包含 Request-Headers, 0 或者多個(gè) Length-Prefixed-Message 以及 EOS力惯。

Request-Headers 直接使用的 HTTP/2 headers碗誉,在 HEADERS 和 CONTINUATION frame 里面派發(fā)。定義的 header 主要有 Call-Definition 以及 Custom-Metadata父晶。Call-Definition 里面包括 Method(其實(shí)就是用的 HTTP/2 的 POST)哮缺,Content-Type 等。而 Custom-Metadata 則是應(yīng)用層自定義的任意 key-value甲喝,key 不建議使用grpc- 開頭尝苇,因?yàn)檫@是為 gRPC 后續(xù)自己保留的。

Length-Prefixed-Message 主要在 DATA frame 里面派發(fā)埠胖,它有一個(gè) Compressed flag 用來表示該 message 是否壓縮糠溜,如果為 1,表示該 message 采用了壓縮直撤,而壓縮算啊定義在 header 里面的 Message-Encoding 里面诵冒。然后后面跟著四字節(jié)的 message length 以及實(shí)際的 message。

EOS(end-of-stream) 會(huì)在最后的 DATA frame 里面帶上了 END_STREAM 這個(gè) flag谊惭。用來表示 stream 不會(huì)在發(fā)送任何數(shù)據(jù)汽馋,可以關(guān)閉了。

Response

Response 主要包含 Response-Headers圈盔,0 或者多個(gè) Length-Prefixed-Message 以及 Trailers豹芯。如果遇到了錯(cuò)誤,也可以直接返回 Trailers-Only驱敲。

Response-Headers 主要包括 HTTP-Status铁蹈,Content-Type 以及 Custom-Metadata 等。Trailers-Only 也有 HTTP-Status 众眨,Content-Type 和 Trailers握牧。Trailers 包括了 Status 以及 0 或者多個(gè) Custom-Metadata。

HTTP-Status 就是我們通常的 HTTP 200娩梨,301沿腰,400 這些,很通用就不再解釋狈定。Status 也就是 gRPC 的 status颂龙, 而 Status-Message 則是 gRPC 的 message。Status-Message 采用了 Percent-Encoded 的編碼方式纽什,具體參考這里措嵌。

如果在最后收到的 HEADERS frame 里面,帶上了 Trailers芦缰,并且有 END_STREAM 這個(gè) flag企巢,那么就意味著 response 的 EOS。

Protobuf

gRPC 的 service 接口是基于 protobuf 定義的让蕾,我們可以非常方便的將 service 與 HTTP/2 關(guān)聯(lián)起來浪规。

  • Path : /Service-Name/{method name}
  • Service-Name : ?( {proto package name} "." ) {service name}
  • Message-Type :{fully qualified proto message name}
  • Content-Type : "application/grpc+proto"

后記

上面只是對 gRPC 協(xié)議的簡單理解或听,可以看到,gRPC 的基石就是 HTTP/2罗丰,然后在上面使用 protobuf 協(xié)議定義好 service RPC神帅。雖然看起來很簡單再姑,但如果一門語言沒有 HTTP/2萌抵,protobuf 等支持,要支持 gRPC 就是一件非常困難的事情了元镀。

轉(zhuǎn)載至公眾號(hào):pingcap2015

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绍填,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子栖疑,更是在濱河造成了極大的恐慌讨永,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遇革,死亡現(xiàn)場離奇詭異卿闹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)萝快,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門锻霎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人揪漩,你說我怎么就攤上這事旋恼。” “怎么了奄容?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵冰更,是天一觀的道長。 經(jīng)常有香客問我昂勒,道長蜀细,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任戈盈,我火速辦了婚禮审葬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘奕谭。我一直安慰自己涣觉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布血柳。 她就那樣靜靜地躺著官册,像睡著了一般。 火紅的嫁衣襯著肌膚如雪难捌。 梳的紋絲不亂的頭發(fā)上膝宁,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天鸦难,我揣著相機(jī)與錄音,去河邊找鬼员淫。 笑死合蔽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的介返。 我是一名探鬼主播拴事,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼圣蝎!你這毒婦竟也來了刃宵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤徘公,失蹤者是張志新(化名)和其女友劉穎牲证,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體关面,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坦袍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了等太。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捂齐。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖澈驼,靈堂內(nèi)的尸體忽然破棺而出辛燥,到底是詐尸還是另有隱情,我是刑警寧澤缝其,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布挎塌,位于F島的核電站,受9級(jí)特大地震影響内边,放射性物質(zhì)發(fā)生泄漏榴都。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一漠其、第九天 我趴在偏房一處隱蔽的房頂上張望嘴高。 院中可真熱鬧,春花似錦和屎、人聲如沸拴驮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽套啤。三九已至,卻和暖如春随常,著一層夾襖步出監(jiān)牢的瞬間潜沦,已是汗流浹背萄涯。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留唆鸡,地道東北人涝影。 一個(gè)月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像争占,于是被迫代替她去往敵國和親燃逻。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容

  • gRPC 是一個(gè)高性能燃乍、通用的開源RPC框架唆樊,其由 Google 主要面向移動(dòng)應(yīng)用開發(fā)并基于HTTP/2 協(xié)議標(biāo)準(zhǔn)...
    劉琨_10f5閱讀 1,297評論 4 6
  • 經(jīng)過很長一段時(shí)間的開發(fā)宛琅,TiDB 終于發(fā)了 RC3刻蟹。RC3 版本對于 TiKV 來說最重要的功能就是支持了 gRP...
    siddontang閱讀 41,931評論 7 80
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)嘿辟,斷路器舆瘪,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • HTTP/2 是一個(gè)二進(jìn)制協(xié)議,這也就意味著它的可讀性幾乎為 0红伦,但幸運(yùn)的是英古,我們還是有很多工具,譬如 Wires...
    angeChen閱讀 1,404評論 0 2
  • 文/海豚Lee 圖片/網(wǎng)絡(luò) 全文約3700余字昙读,閱讀時(shí)間約7分鐘 01 最近召调,我看了一部很有趣的日本電影《佐賀的超...
    海豚的窗戶閱讀 843評論 0 1