RPC協議之爭和選型要點

《Netty 進階之路》、《分佈式服務框架原理與實踐》作者李林鋒深入剖析RPC協議之爭和選型問題。李林鋒此後還將在 InfoQ 上開設 Netty 專題持續出稿,感興趣的同學可以持續關注。

1. 協議之爭背景

1.1 RPC調用的協議選擇

RPC調用的協議選擇包含兩部分:

1.協議棧:廣義上協議棧可以分爲公有協議和私有協議,例如HTTP、SMPP、WebService等都是公有協議;如果是某個公司或者組織內部自定義、自己使用的協議,沒有被國際標準化組織接納和認可的,往往劃爲私有協議,例如Thrift協議。

2.序列化方式:同一種協議也可以承載多種序列化方式,以HTTP協議爲例,它可以承載文本類序列化方式,例如:XML、JSON等,也可以承載二進制序列化方式,例如谷歌的Protobuf。

不同的協議選擇,對RPC調用的性能、開發難度和問題定位效率都有影響,因此,選擇哪種協議,對RPC框架而言至關重要。由於各個協議都有自己的優缺點,因此很多框架在技術選型時都非常糾結。各種觀點存在激烈交鋒,有的看重性能和時延、有的更看重跨語言和可維護性。協議的選擇是RPC框架構建的一個技術難點。

1.2 私有協議流行的原因

私有協議本質上是廠商內部發展和採用的標準,除非授權,其他廠商一般無權使用該協議。私有協議也稱非標準協議,就是未經國際或國家標準化組織採納或批准,由某個企業自己制訂,協議實現細節不願公開,只在企業自己生產的設備之間使用的協議。私有協議具有封閉性、壟斷性、排他性等特點。如果網上大量存在私有(非標準)協議,現行網絡或用戶一旦使用了它,後進入的廠家設備就必須跟着使用這種非標準協議,才能夠互連互通,否則根本不可能進入現行網絡。這樣,使用非標準協議的廠家就實現了壟斷市場的願望。

儘管私有協議具有壟斷性的特徵,但並非所有的私有協議設計者的初衷就是爲了壟斷。由於現代軟件系統的複雜性,一個大型軟件系統往往會被人爲地拆分成多個模塊,另外隨着移動互聯網的興起,網站的規模也越來越大,業務的功能越來越多,爲了能夠支撐業務的發展,往往需要集羣和分佈式部署,這樣,各個模塊之間就要進行跨節點通信。

在傳統的Java應用中,通常使用以下4種方式進行跨節點通信。

1.通過RMI進行遠程服務調用。

2.通過Java的Socket+Java序列化的方式進行跨節點調用。

3.利用一些開源的RPC框架進行遠程服務調用,例如Facebook的Thrift,Google的gRPC等。

4.利用標準的公有協議進行跨節點服務調用,例如HTTP+XML、Restful+JSON或者WebService。

跨節點的遠程服務調用,除了鏈路層的物理連接外,還需要對請求和響應消息進行編解碼。在請求和應答消息本身以外,也需要攜帶一些其他控制和管理類指令,例如鏈路建立的握手請求和響應消息、鏈路檢測的心跳消息等。當這些功能組合到一起之後,就會形成私有協議。

私有協議的優點:靈活性高,可以按照業務的使用場景來設計和優化,在某個公司或者組織內部使用時也可以按需定製和演進,所以大部分RPC框架都支持私有二進制協議,例如阿里的Dubbo、華爲的ServiceComb、Apache的Thrift等。

1.3 序列化方式

當進行遠程跨進程服務調用時,需要把被傳輸的數據結構/對象序列化爲字節數組或者ByteBuffer。而當遠程服務讀取到ByteBuffer對象或者字節數組時,需要將其反序列化爲原始的數據結構/對象。利用序列化框架可以實現上述轉換工作。

Java序列化從JDK 1.1版本就已經提供,它不需要添加額外的類庫,只需實現java.io.Serializable並生成序列ID即可,因此,它從誕生之初就得到了廣泛的應用。

但是在遠程服務調用(RPC)時,很少直接使用Java序列化進行消息的編解碼和傳輸,這又是什麼原因呢?下面通過分析Java序列化的缺點來找出答案:

缺點1:無法跨語言,是Java序列化最致命的問題。對於跨進程的服務調用,服務提供者可能會使用C++或者其他語言開發,當我們需要和異構語言進程交互時,Java序列化就難以勝任。由於Java序列化技術是Java語言內部的私有協議,其它語言並不支持,對於用戶來說它完全是黑盒。對於Java序列化後的字節數組,別的語言無法進行反序列化,這就嚴重阻礙了它的應用。事實上,目前幾乎所有流行的Java RCP通信框架,都沒有使用Java序列化作爲編解碼框架,原因就在於它無法跨語言,而這些RPC框架往往需要支持跨語言調用。

缺點2:相比於業界的一些序列化框架,Java默認的序列化效能較低,主要體現在:序列化之後的字節數組體積較大,性能較低。在同等情況下,編碼後的字節數組越大,存儲的時候就越佔空間,存儲的硬件成本就越高,並且在網絡傳輸時更佔帶寬,導致系統的吞吐量降低。Java序列化後的碼流偏大也一直被業界所詬病,導致它的應用範圍受到了很大限制。

當前比較流行的序列化方式可以分爲兩大類:

1.文本類序列化方式:主要包括JSON和XML,它們的優點是:支持跨語言、可讀性好、配套的支持工具比較全。缺點就是:序列化之後的碼流比較大、冗餘內容多,性能相對比較差。

2.私有的二進制類序列化方式:比較流行的有Thrift序列化框架、MessagePack和谷歌的Protobuf框架。它的優點是性能高,缺點就是可讀性差,支撐的工具鏈不健全。

2. RPC協議選型要點

2.1 協議棧的選擇

2.1.1 公有協議

儘管公有協議種類繁多,例如之前非常流行的WebService、WADL等,但目前來看,如果選擇公有協議,HTTP協議還是首選,具有Rest風格的Restful + JSON接口是當前最流行的方式。

RPC協議如果選擇Restful + JSON,會帶來如下幾個優點:

1.踐行API First理念:通過使用Swagger YAML定義API,服務端和客戶端都基於API定義,通過Swagger代碼生成工具生成不同語言的接口和模型定義類庫,客戶端不需要從服務端導入接口定義類庫,也不需要配置Maven依賴,這樣就實現了雙方依賴的解耦:

圖1 基於Swagger代碼工具生成服務端和客戶端代碼

除了代碼生成,利用swagger editor和swagger ui工具,可以在線定義和維護API接口的契約,實現接口API的在線化管理,更好的管控API變更,示例如下:

圖2 基於Swagger UI在線管理Restful API

對於RPC接口的測試,由於是Restful風格的,利用一些開源的契約測試框架,可以方便的進行契約化測試:

圖3 對Restful風格的RPC API做契約化測試

2.天生語言中立:HTTP協議和JSON序列化天生就是語言中立的,對於RPC框架,在架構上能夠支持多語言非常重要。不同的業務場景,適合不同的語言,例如後端複雜業務邏輯使用Java開發效率更高,對於API網關或者邊緣服務,適合GO語言。對於一些序列化框架,由於使用了一些特定語言的特性,例如Exception、泛型等,這對於跨語言演進是個災難,像protostuff就綁定了Java語言。

3.內部和外部API接口的統一:RPC服務通常只開放給內部的客戶端做調用,如果需要開放給外部的端側、其它渠道調用,往往需要前置一個API網關或者Edge Service,用來做安全接入、權限管理、統一流控、灰度發佈等。組網示例如下所示:

圖4 對RPC服務對外開放組網圖

如果RPC調用選擇Restful API,則對外開放時,API網關/Edge Service只要做安全等相關功能即可,消息可以透傳轉發給後端RPC服務。如果後端RPC服務選擇私有協議,將私有協議直接開放給合作伙伴顯然是不合適的,這就需要在API網關上爲後端RPC服務定義API接口,同時做消息映射和轉換,最終形成對外開放一套API接口,內部使用又一套API接口,但是功能卻相同或者類似,這增加了接口的開發和維護工作量。

目前主流的API Gateway都支持直接導入Swagger定義的API,自動生成併發布API接口,以AWS的API Gateway爲例,如下所示:

圖5 AWS基於Swagger接口定義生成API(來自AWS官網示例教程)

當RPC服務開放給內部和外部是同一套API接口時,接口的開發和維護工作量都會減少很多。

4.問題定位更簡單:HTTP協議 + JSON文本序列化方式,更容易調試,抓包的碼流解讀也更容易,相反如果是二進制私有協議,碼流需要人工解讀,難度較高。

2.1.2 私有協議

絕大多數的私有協議傳輸層都基於TCP/IP,對於Java語言,可以利用Netty的NIO TCP協議棧進行私有協議的定製和開發。

私有協議的格式往往存在較大差異,但是對於大多數RPC框架的私有協議,往往會包含如下幾個字段:

1.消息頭:消息頭中通常會包含校驗碼、消息長度、消息類型、消息/會話ID、需要調用的RPC接口名、方法名,以及擴展的消息頭,通常是個類似Map的結構,用於隱式傳參。

2.消息體:對於請求消息,主要就是請求參數,對於響應,就是響應對象以及結果碼。

下面以基於Netty開發的Netty協議棧爲例進行說明,它的協議棧模型如下所示:

圖6 Netty協議棧通信交互圖

Netty協議棧承載了業務內部各模塊之間的消息交互和RPC調用,它的主要功能包括:

1.基於Netty的NIO通信框架,提供高性能的異步通信能力。

2.提供消息的編解碼框架,可以實現POJO的序列化和反序列化。

3.提供基於IP地址的白名單接入認證機制。

4.鏈路的有效性校驗機制。

5.鏈路的斷連重連機制。

RPC協議棧交互的流程說明如下:

1.Netty協議棧客戶端發送握手請求消息,攜帶節點ID等有效身份認證信息。

2.Netty協議棧服務端對握手請求消息進行合法性校驗,包括節點ID有效性校驗、節點重複登錄校驗和IP地址合法性校驗,校驗通過後,返回登錄成功的握手應答消息。

3.鏈路建立成功之後,客戶端發送業務消息。

4.鏈路成功之後,服務端發送心跳消息。

5.鏈路建立成功之後,客戶端發送心跳消息。

6.鏈路建立成功之後,服務端發送業務消息。

7.服務端退出時,服務端關閉連接,客戶端感知對方關閉連接後,被動關閉客戶端連接。

Netty協議棧的消息定義如下:

消息包含消息頭和消息體:

名 稱 類 型 長 度 描 述
header Header 變長 消息頭定義
body Object 變長 對於請求消息,它是方法的參數(作爲示例,只支持攜帶一個參數);對於響應消息,它是返回值

其中,消息頭定義如下:

名 稱 類 型 長 度 描 述
crcCode 整型 int 32 Netty消息的校驗碼,它由三部分組成: 1)0xABEF:固定值,表明該消息是Netty協議消息,2個字節; 2)主版本號:1~255,1個字節; 3)次版本號:1~255,1個字節。 crcCode = 0xABEF + 主版本號 + 次版本號

續表

名 稱 類 型 長 度 描 述
length 整型 int 32 消息長度,整個消息,包括消息頭和消息體
sessionID 長整型long 64 集羣節點內全局唯一,由會話ID生成器生成
type Byte 8 0:業務請求消息; 1:業務響應消息; 2:業務ONE WAY消息(既是請求又是響應消息); 3:握手請求消息; 4:握手應答消息; 5:心跳請求消息; 6:心跳應答消息。
priority Byte 8 消息優先級:0~255
interfaceName String 變長
methodName String 變長
attachment Map<String, Object> 變長 可選字段,用於擴展消息頭

對於私有協議棧的構建,需要考慮到如下幾點,整體成本較高:

1.支持的數據結構類型,以及採用的序列化方式。

2.握手和接入認證。

3.心跳檢測機制。

4.斷連和重連機制。

5.併發連接數的控制。

6.異常、畸形碼流的檢測和保護。

7.流量限制和整形。

8.連接池。

9.網絡閃斷、宕機保護、消息緩存和重發機制等。

儘管私有協議棧構建成本較高,但是它的優勢也很明顯:

1.靈活性、可定製性更好,可以針對業務特定場景做優化。

2.可以實現更高的性能。

2.2 序列化框架

2.2.1 選型的原則

如果對性能要求不高,則建議選用JSON,否則可以選擇一些業界主流的序列化框架,需要注意的是,對於序列化框架的選擇,一定要考慮跨語言性,如果綁定特定語言,會對未來RPC框架支持多語言帶來極大的困難。

2.2.2 Google的Protobuf

Protobuf全稱Google Protocol Buffers,它由谷歌開源而來,在谷歌內部久經考驗。它將數據結構以.proto文件進行描述,通過代碼生成工具可以生成對應數據結構的POJO對象和Protobuf相關的方法和屬性。

它的特點如下:

1.結構化數據存儲格式(XML,JSON等)。

2.高效的編解碼性能。

3.語言無關、平臺無關、擴展性好。

4.官方支持Java、C++和Python三種語言(社區會支持更多中語言)。

Protobuf的優點主要有兩個:

1.IDL契約:利用數據描述文件對數據結構進行說明,可以實現語言和平臺無關,通過標識字段的順序,可以實現協議的前向兼容,同時提供代碼生成工具,可以生成各種語言的服務端和客戶端代碼。

2.性能:相比於其它序列化框架,它的性能更優,數據對比如下所示:

圖7 Protobuf編解碼和其他幾種序列化框架的響應時間對比

2.2.3 Apache Thrift

Thrift源於Facebook,在2007年Facebook將Thrift作爲一個開源項目提交給Apache基金會。對於當時的Facebook來說,創造Thrift是爲了解決Facebook各系統間大數據量的傳輸通信以及系統之間語言環境不同需要跨平臺的特性,因此Thrift可以支持多種程序語言,如C++、Cocoa、Erlang、Haskell、Java、Ocami、Perl、PHP、Python、Ruby和Smalltalk。

在多種不同的語言之間通信,Thrift可以作爲高性能的通信中間件使用,它支持數據(對象)序列化和多種類型的RPC服務。Thrift適用於靜態的數據交換,需要先確定好它的數據結構,當數據結構發生變化時,必須重新編輯IDL文件,生成代碼和編譯,這一點跟其他IDL工具相比可以視爲是Thrift的弱項。Thrift適用於搭建大型數據交換及存儲的通用工具,對於大型系統中的內部數據傳輸,相對於JSON和XML在性能和傳輸大小上都有明顯的優勢。

Thrift主要由5部分組成:

  1. 語言系統以及IDL編譯器:負責由用戶給定的IDL文件生成相應語言的接口代碼;

  2. TProtocol:RPC的協議層,可以選擇多種不同的對象序列化方式,如JSON和Binary;

  3. TTransport:RPC的傳輸層,同樣可以選擇不同的傳輸層實現,如socket、NIO、MemoryBuffer等;

  4. TProcessor:作爲協議層和用戶提供的服務實現之間的紐帶,負責調用服務實現的接口;

  5. TServer:聚合TProtocol、TTransport和TProcessor等對象。

需要重點關注的是編解碼框架,與之對應的就是TProtocol。由於Thrift的RPC服務調用和編解碼框架綁定在一起,所以,通常我們使用Thrift的時候會採取RPC框架的方式。但是,它的TProtocol編解碼框架還是可以以類庫的方式獨立使用的。

與Protobuf比較類似的是,Thrift通過IDL描述接口和數據結構定義,它支持8種Java基本類型、Map、Set和List,支持可選和必選定義,功能非常強大。因爲可以定義數據結構中字段的順序,所以它也可以支持協議的前向兼容。

Thrift支持三種比較典型的編解碼方式。

  • 通用的二進制編解碼

  • 壓縮二進制編解碼

  • 優化的可選字段壓縮編解碼

由於支持二進制壓縮編解碼,Thrift的編解碼性能表現也相當優異,遠遠超過Java序列化和RMI等。

2.2.4 MessagePack序列化框架

MessagePack是一個高效的二進制序列化框架,它像JSON一樣支持不同語言間的數據交換,但是它的性能更快,序列化之後的碼流也更小。MessagePack提供了對多語言的支持,官方支持的語言如下:Java、Python、Ruby、Haskell、C#、OCaml、Lua、Go、C、C++等。

MessagePack的Java API非常簡單,如果使用MessagePack進行開發,只需要導入MessagePack maven依賴:

<dependency>

   <groupId>org.msgpack</groupId>

   <artifactId>msgpack</artifactId>

   <version>${msgpack.version}</version>

</dependency>

它的API使用示例如下:

List<String> src = new ArrayList<String>();

src.add("msgpack");

src.add("kumofs");

src.add("viver");

MessagePack msgpack = new MessagePack();

byte[] raw = msgpack.write(src);

List<String> dst1 =

msgpack.read(raw, Templates.tList(Templates.TString));

2.2.5. 選型建議

上面列舉的幾種序列化框架各有優缺點,如果選用,則建議從如下幾個維度做對比:

1.支持數據類型的豐富度。

2.性能對比測試,主要包括 序列化和反序列化的耗時、CPU和內存佔用,以及序列化之後的碼流大小。

3.儘管都支持跨語言,但是由於支持的語言豐富度不同,業務需要根據自己RPC框架未來可能支持的語言做選擇。

3. RPC協議棧實踐

3.1 Restful API的優化

使用Restful API可以帶來很多收益:

  • API接口更加規範和標準,可以通過Swagger API規範來描述服務接口,並生成客戶端和服務端代碼。

  • Restful API可讀性更好,也更容易維護。

  • 服務提供者和消費者基於API契約,雙方可以解耦,不需要在客戶端引入SDK和類庫的直接依賴,未來的獨立升級也更方便。

  • 內外可以使用同一套API,非常容易開放給外部或者合作伙伴使用,而不是對內和對外維護兩套不同協議的API。

通常,對外開放的API使用Restful是通用的做法,但是在系統內部,例如商品中心和訂單中心,RPC調用使用 Restful風格的API作爲微服務的API,卻可能存在性能風險。

3.1.1 HTTP1.X的性能問題

如果採用的Restful API 底層使用的HTTP協議棧是同步阻塞I/O,則服務端的處理性能將大打折扣:

1.性能問題:一連接一線程模型導致服務端的併發接入數和系統吞吐量受到極大限制。

2.可靠性問題:由於I/O操作採用同步阻塞模式,當網絡擁塞或者通信對端處理緩慢會導致I/O線程被掛住,阻塞時間無法預測。

3.可維護性問題:I/O線程數無法有效控制、資源無法有效共享(多線程併發問題),系統可維護性差。

如果HTTP協議棧採用了異步非阻塞I/O模型(例如Netty、Servlet3.X版本),則可以解決同步阻塞I/O的問題,帶來如下收益:

1.同一個I/O線程可以並行處理多個客戶端鏈接,有效降低了I/O線程數量,提升了資源調度利用率

2.讀寫操作都是非阻塞的,不會因爲對端處理慢、網絡時延大等導致的I/O線程被阻塞

相比於TCP類協議,例如Thrift, 採用了非阻塞I/O的HTTP/1.X協議仍然存在性能問題,原因如下所示:

如果HTTP協議棧採用了異步非阻塞I/O模型(例如Netty、Servlet3.X版本),則可以解決同步阻塞I/O的問題,帶來如下收益:

  • 同一個I/O線程可以並行處理多個客戶端鏈接,有效降低了I/O線程數量,提升了資源調度利用率。

  • 讀寫操作都是非阻塞的,不會因爲對端處理慢、網絡時延大等導致的I/O線程被阻塞。

相比於TCP類協議,例如Thrift, 採用了非阻塞I/O的HTTP/1.X協議仍然存在性能問題,原因如下所示:

圖8 HTTP/1.X 請求-響應模式

由於HTTP協議是無狀態的,客戶端發送請求之後,必須等待接收到服務端響應之後,才能繼續發送請求(非websocket、pipeline等模式)。在某一個時刻,鏈路上只存在單向的消息流,實際上把TCP的雙工變成了單工模式。如果服務端響應耗時較大,則單個HTTP鏈路的通信性能嚴重下降,只能通過不斷的新建連接來提升I/O性能。但這也會帶來很多副作用,例如句柄數的增加、I/O線程的負載加重等。顯而易見,修8條單向車道的成本遠遠高於修一條雙向8車道的成本。

除了無狀態導致的鏈路傳輸性能差之外,HTTP/1.X還存在如下幾個影響性能的問題:

  • HTTP客戶端超時之後,由於協議是無狀態的,客戶端無法對請求和響應進行關聯,只能關閉鏈路重連,反覆的建鏈會增加成本開銷和時延(如果客戶端選擇不關閉鏈路,繼續發送新的請求,服務端可能會把上一條客戶端認爲超時的響應返回回去,也可能按照HTTP協議規範直接關閉鏈路,無路哪種處理,都會導致鏈路被關閉)。如果採用傳統的RPC私有協議,請求和響應可以通過消息ID或者會話ID做關聯,某條消息的超時並不需要關閉鏈路,只需要丟棄該消息重發即可。

  • HTTP本身包含文本類型的協議消息頭,佔用一些字節。另外,採用JSON類文本的序列化方式,報文相比於傳統的私有RPC協議也大很多,降低了傳輸性能。

  • 服務端無法主動推送響應。

如果業務對性能和資源成本要求非常苛刻,在選擇使用基於HTTP/1.X的Restful API 代替私有RPC API(通常是基於TCP的二進制私有協議)時就要三思;反之,如果業務對性能要求較低,或者在硬件成本和開放性、規範性上更看重後者,則使用Restful API也無妨。

3.1.2 優化方案

如果選擇Restful API作爲內部RPC或者微服務的接口協議,則建議使用HTTP/2.0協議來承載,它的優點如下:支持雙向流、消息頭壓縮、單TCP的多路複用、服務端推送等特性,某個RPC調用超時也不需要關閉HTTP連接,只需要關閉對應的Stream流即可,這樣可以避免大量超時時頻繁的HTTP連接重建,有效解決傳統HTTP/1.X協議遇到的問題,效果與RPC的TCP私有協議接近,採用HTTP/2的gRPC Steaming通信模式示例如下:

圖9 gRPC基於HTTP/2的服務端streaming調用

3.2 Apache ServiceComb的多協議實踐

3.2.1 標準化的服務契約

ServiceComb使用yaml文件格式定義服務契約,推薦使用Swagger Editor工具來編寫契約,可檢查語法格式及自動生成API文檔。服務契約與具體協議無關係,無論使用RPC二進制協議通信還是使用標準的Restful + JSON,都可以使用Swagger API來描述和定義微服務接口。

接口定義完成之後,將契約文件放到"resources/microservices"或者"resources/application"目錄下即可,目錄結構如下所示:

圖10 微服務接口契約定義

3.2.2 通信協議

ServiceComb實現了兩種網絡通道,包括Rest和Highway,均支持TLS加密傳輸。其中,REST網絡通道將服務以標準Restful形式發佈,調用端兼容直接使用Http client使用標準Restful形式進行調用。

Rest協議棧支持兩種實現方式:

1.REST over Servlet對應使用web容器部署運行,需要新建一個servlet工程將微服務包裝起來,打成war包,加載到web容器中啓動運行。

2.REST over Vertx通信通道對應使用standalone部署運行模式,可直接通過main函數拉起。使用REST over Vertx網絡通道需要在maven pom文件中添加如下依賴:

圖11 REST over Vertx通信方式

除了對Rest的支持,在一些需要高性能、低時延的場景,使用私有二進制性能會更高一些。Highway是ServiceComb的高性能私有協議,用戶可在有特殊性能需求的場景下選用。

使用Highway網絡通道需要在maven pom文件中添加如下依賴:

圖12 Highway通信方式

ServiceComb的通信協議有如下幾個特點:

1.協議與服務接口契約定義沒關係,使用Swagger定義的API接口,仍然可以使用二進制Highway私有協議進行RPC調用。

2.協議與業務代碼無耦合關係,一套業務接口,可以選擇發佈成哪種協議。支持同一個服務接口發佈成多種協議,客戶端可以根據自己的需要選擇哪種協議方式調用。

3.2.3 對HTTP/2協議的支持

ServiceComb提供了對HTTP/2的支持,用於解決傳統HTTP/1.1的性能問題,它支持2種HTTP/2通信方式:

1.h2(Http2 + TLS):服務端在配置服務監聽地址時,可以通過在地址後面追加?sslEnabled=true開啓TLS通信,示例如下:

圖13 h2通信方式配置

2.h2c(Http2 without TLS):服務端在配置服務監聽地址時,可以通過在地址後面追加?protocol=http2啓用h2c通信:

圖14 h2c通信方式配置

3.2.4.性能對比數據

Restful和Highway(私有二進制協議)兩種協議在不同模式的性能對比數據如下所示:

圖15 性能對比數據

通過上述性能測試數據對比可以發現,私有的二進制協議Highway(TCP + Protobuf)比Restful+JSON協議整體性能高2倍+左右。如果Restful+JSON採用HTTP/1.1承載,無法實現鏈路的多路複用,一旦RPC調用超時就會頻繁重建鏈路,可靠性相比於TCP私有協議會差很多。

4. 總結

4.1.協議與接口的解耦

無論是選擇Restful(HTTP協議)還是私有二進制協議,總是存在各自的優缺點,對於比較複雜的業務場景,可能多種協議都需要支持,這就要求RPC框架必須實現接口定義與協議本身的解耦,即業務可以平滑的切換協議,上層應用不需要感知。

4.2 RPC協議的定製和擴展

一個比較好的協議往往具備較強的擴展性,協議的擴展主要體現在兩點:

1.協議棧本身的擴展,例如基於協議框架擴展出另一種協議。

2.協議棧採用的序列化框架的擴展。

對於RPC框架而言,更有價值的是序列化框架的擴展,可以在協議頭中增加一個標識協議類型的字段,RPC框架根據協議類型來調用對應的序列化框架,實現業務自定義的序列化和反序列化。

4.3 一些技術難點

不同的序列化框架,對數據類型的支持不同,對字段是否支持亂序也存在差異,所以,從某種程度看,協議和序列化方式完全與業務接口解耦也是很困難的,會有很多約束和限制,所以需要辨證的看待接口和協議的解耦,儘量做到對業務的影響最小、以及約束和限制的規範化。

5. 作者簡介

李林鋒,10年Java NIO、平臺中間件設計和開發經驗,精通Netty、Mina、分佈式服務框架、API Gateway、PaaS等,《Netty進階之路》、《分佈式服務框架原理與實踐》作者。目前在華爲終端應用市場負責業務微服務化、雲化、全球化等相關設計和開發工作。

聯繫方式:新浪微博 Nettying 微信:Nettying

Email:[email protected]

延伸閱讀:

深入剖析通信層和 RPC 調用的異步化 (上)
深入剖析通信層和 RPC 調用的異步化 (下)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章