深刻理解 RPC 消息協議設計

本節咱們開始講解 RPC 的消息協議設計背後的基本原理,瞭解 RPC 的協議開發背後有哪些須要考慮的基本點。在通曉原理以後,咱們就能夠本身設計一套協議來開發屬於本身的 RPC 系統。算法

本節主要涉及的知識點和它們之見的關係以下圖:json

對於一串消息流,咱們必須能肯定消息邊界,提取出單條消息的字節流片斷,而後對這個片斷按照必定的規則進行反序列化來生成相應的消息對象。數組

消息表示指的是序列化後的消息字節流在直觀上的表現形式,它看起來是對人類友好仍是對計算機友好。文本形式對人類友好,二進制形式對計算機友好。性能優化

每一個消息都有其內部字段結構,結構構成了消息內部的邏輯規則,程序要按照結構規則來決定字段序列化的順序。服務器

接下來,咱們初步詳細拆解。網絡

消息邊界架構

RPC 須要在一條 TCP 連接上進行屢次消息傳遞。在連續的兩條消息之間必須有明確的分割規則,以便接收端能夠將消息分割開來,這裏的接收端能夠是 RPC 服務器接收請求,也能夠是 RPC 客戶端接收響應。併發

基於 TCP 連接之上的單條消息若是過大,就會被網絡協議棧拆分爲多個數據包進行傳送。若是消息太小,網絡協議棧可能會將多個消息組合成一個數據包進行發送。對於接收端來講它看到的只是一串串的字節數組,若是沒有明確的消息邊界規則,接收端是無從知道這一串字節數組到底是包含多條消息仍是隻是某條消息的一部分。app

比較經常使用的兩種分割方式是特殊分割符法和長度前綴法。分佈式

消息發送端在每條消息的末尾追加一個特殊的分割符,而且保證消息中間的數據不能包含特殊分割符。好比最爲常見的分割符是 。當接收端遍歷字節數組時發現了 ,就當即能夠判定 以前的字節數組是一條完整的消息,能夠傳遞到上層邏輯繼續進行處理。HTTP 和 Redis 協議就大量使用了 分割符。此種消息通常要求消息體的內容是文本消息。

消息發送端在每條消息的開頭增長一個 4 字節長度的整數值,標記消息體的長度。這樣消息接受者首先讀取到長度信息,而後再讀取相應長度的字節數組就能夠將一個完整的消息分離出來。此種消息比較經常使用於二進制消息。

基於特殊分割符法的優勢在於消息的可讀性比較強,能夠直接看到消息的文本內容,缺點是不適合傳遞二進制消息,由於二進制的字節數組裏面很容易就冒出連續的兩個字節內容正好就是 分割符的 ascii 值。若是須要傳遞的話,通常是對二進制進行 base64 編碼轉變成普通文本消息再進行傳送。

基於長度前綴法的優勢和缺點同特殊分割符法正好是相反的。長度前綴法由於適用於二進制協議,因此可讀性不好。可是對傳遞的內容自己沒有特殊限制,文本和內容皆能夠傳輸,不須要進行特殊處理。HTTP 協議的 Content-Length 頭信息用來標記消息體的長度,這個也能夠當作是長度前綴法的一種應用。

HTTP 協議是一種基於特殊分割符和長度前綴法的混合型協議。好比 HTTP 的消息頭採用的是純文本外加 分割符,而消息體則是經過消息頭中的 Content-Type 的值來決定長度。HTTP 協議雖然被稱之爲文本傳輸協議,可是也能夠在消息體中傳輸二進制數據數據的,例如音視頻圖像,因此 HTTP 協議被稱之爲「超文本」傳輸協議。

消息的結構

每條消息都有它包含的語義結構信息,有些消息協議的結構信息是顯式的,還有些是隱式的。好比 json 消息,它的結構就能夠直接經過它的內容體現出來,因此它是一種顯式結構的消息協議。

json 這種直觀的消息協議的可讀性很是棒,可是它的缺點也很明顯,有太多的冗餘信息。好比每一個字符串都使用雙引號來界定邊界,key/value 之間必須有冒號分割,對象之間必須使用大括號分割等等。這些還只是冗餘的小頭,最大的冗餘還在於連續的多條 json 消息即便結構徹底同樣,僅僅只是 value 的值不同,也須要發送一樣的 key 字符串信息。

消息的結構在同一條消息通道上是能夠複用的,好比在創建連接的開始 RPC 客戶端和服務器之間先交流協商一下消息的結構,後續發送消息時只須要發送一系列消息的 value 值,接收端會自動將 value 值和相應位置的 key 關聯起來,造成一個完成的結構消息。在 Hadoop 系統中普遍使用的 avro 消息協議就是經過這種方式實現的,在 RPC 連接創建之處就開始交流消息的結構,後續消息的傳遞就能夠節省不少流量。

消息的隱式結構通常是指那些結構信息由代碼來約定的消息協議,在 RPC 交互的消息數據中只是純粹的二進制數據,由代碼來肯定相應位置的二進制是屬於哪一個字段。好比下面的這段代碼

若是純粹看消息內容是沒法知道節點消息內容中的哪些字節的含義,它的消息結構是經過代碼的結構順序來肯定的。這種隱式的消息的優勢就在於節省傳輸流量,它徹底不須要傳輸結構信息。

消息壓縮

若是消息的內容太大,就要考慮對消息進行壓縮處理,這能夠減輕網絡帶寬壓力。可是這同時也會加劇 CPU 的負擔,由於壓縮算法是 CPU 計算密集型操做,會致使操做系統的負載加劇。因此,最終是否進行消息壓縮,必定要根據業務狀況加以權衡。

若是肯定壓縮,那麼在選擇壓縮算法包時,務必挑選那些底層用 C 語言實現的算法庫,由於 Python 的字節碼執行起來太慢了。比較流行的消息壓縮算法有 Google 的 snappy 算法,它的運行性能很是好,壓縮比例雖然不是最優的,可是離最優的差距已經不是很大。阿里的 SOFA RPC 就使用了 snappy 做爲協議層壓縮算法。

流量的極致優化

開源的流行 RPC 消息協議每每對消息流量優化到了極致,它們經過這種方式來打動用戶,吸引用戶來使用它們。好比對於一個整形數字,通常使用 4 個字節來表示一個整數值。

可是通過研究發現,消息傳遞中大部分使用的整數值都是很小的非負整數,若是所有使用 4 個字節來表示一個整數會很浪費。因此就發明了一個類型叫變長整數varint。數值很是小時,只須要使用一個字節來存儲,數值稍微大一點可使用 2 個字節,再大一點就是 3 個字節,它還能夠超過 4 個字節用來表達長整形數字。

其原理也很簡單,就是保留每一個字節的最高位的 bit 來標識是否後面還有字節,1 表示還有字節須要繼續讀,0 表示到讀到當前字節就結束。

那若是是負數該怎麼辦呢?-1 的 16 進制數是 0xFFFFFFFF,若是要按照這個編碼那豈不是要 6 個字節才能存的下。-1 也是很是常見的整數啊。

因而 zigzag 編碼來了,專門用來解決負數問題。zigzag 編碼將整數範圍一一映射到天然數範圍,而後再進行 varint 編碼。

zigzag 將負數編碼成正奇數,正數編碼成偶數。解碼的時候遇到偶數直接除 2 就是原值,遇到奇數就加 1 除 2 再取負就是原值。

小結

如今咱們知道了 RPC 消息結構的設計原理,遵循這些基本方法,就能夠創造出一個又一個不一樣的消息協議。

在此我向你們推薦一個Java高級羣 :725633148 裏面會分享一些資深架構師錄製的視頻錄像:(有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構)等這些成爲架構師必備的知識體系 進羣立刻免費領取,目前受益良多!

相關文章
相關標籤/搜索