1. 程式人生 > >服務化實戰之 dubbo、dubbox、motan、thrift、grpc等RPC框架比較及選型

服務化實戰之 dubbo、dubbox、motan、thrift、grpc等RPC框架比較及選型

分布式系統 線程 ins tno 大小 實施 基礎設施 child shift

概述

前段時間項目要做服務化,所以我比較了現在流行的幾大RPC框架的優缺點以及使用場景,最終結合本身項目的實際情況選擇了使用dubbox作為rpc基礎服務框架。下面就簡單介紹一下RPC框架技術選型的過程。

RPC簡述

該系列文章將講述以下RPC框架的helloword實例以及其實現原理簡述,由於每一種RPC框架的原理實現不同且都比較復雜,如果想深入研究還請自行到官網或者其他技術博客學習。
RPC框架職責
RPC框架要向調用方屏蔽各種復雜性,要向服務提供方也屏蔽各類復雜性:

  • 調用方感覺就像調用本地函數一樣
  • 服務提供方感覺就像實現一個本地函數一樣來實現服務

RPC框架是架構(微)服務化的首要基礎組件,它能大大降低架構微服務化的成本,提高調用方與服務提供方的研發效率,屏蔽跨進程調用函數(服務)的各類復雜細節
RPC框架的職責是:讓調用方感覺就像調用本地函數一樣調用遠端函數、讓服務提供方感覺就像實現一個本地函數一樣來實現服務

對於不是很理解服務化的同學可以看看這兩篇文章:
服務化架構的演進與實踐
淺談服務化架構

服務化的好處
互聯網架構為什麽要做服務化?

服務架構的拆分原則

來源:李林峰的文章:華為內部如何實施微服務架構?基本就靠這5大原則

服務拆分原則:圍繞業務功能進行垂直和水平拆分。大小粒度是難點,也是團隊爭論的焦點。

不好的實踐

  • 以代碼量作為衡量標準,例如500行以內。
  • 拆分的粒度越小越好,例如以單個資源的操作粒度為劃分原則。

建議的原則

  • 功能完整性、職責單一性。
  • 粒度適中,團隊可接受。
  • 叠代演進,非一蹴而就。
  • API的版本兼容性優先考慮。

代碼量多少不能作為衡量微服務劃分是否合理的原則,因為我們知道同樣一個服務,功能本身的復雜性不同,代碼量也不同。還有一點需要重點強調,在項目剛開始的時候,不要期望微服務的劃分一蹴而就。
微服務架構的演進,應該是一個循序漸進的過程。在一個公司、一個項目組,它也需要一個循序漸進的演進過程。一開始劃不好,沒有關系。當演進到一個階段時,微服務的部署、測試和運維等成本都非常低的時候,這對於你的團隊來說就是一個好的微服務。

服務架構的開發原則

微服務的開發還會面臨依賴滯後的問題。例如:A要做一個身份證號碼校驗,依賴服務提供者B。由於B把身份證號碼校驗服務的開發優先級排的比較低,無法滿足A的交付時間點。A會面臨要麽等待,要麽自己實現一個身份證號碼校驗功能。

以前單體架構的時候,大家需要什麽,往往喜歡自己寫什麽,這其實是沒有太嚴重的依賴問題。但是到了微服務時代,微服務是一個團隊或者一個小組提供的,這個時候一定沒有辦法在某一個時刻同時把所有的服務都提供出來,“需求實現滯後”是必然存在的。

一個好的實踐策略就是接口先行,語言中立,服務提供者和消費者解耦,並行開發,提升產能。無論有多少個服務,首先需要把接口識別和定義出來,然後雙方基於接口進行契約驅動開發,利用Mock服務提供者和消費者,互相解耦,並行開發,實現依賴解耦。

采用契約驅動開發,如果需求不穩定或者經常變化,就會面臨一個接口契約頻繁變更的問題。對於服務提供者,不能因為擔心接口變更而遲遲不對外提供接口,對於消費者要擁抱變更,而不是抱怨和抵觸。要解決這個問題,一種比較好的實踐就是管理 + 技術雙管齊下:

  • 允許接口變更,但是對變更的頻度要做嚴格管控。
  • 提供全在線的API文檔服務(例如Swagger UI),將離線的API文檔轉成全在線、互動式的API文檔服務。
  • API變更的主動通知機制,要讓所有消費該API的消費者能夠及時感知到API的變更。
  • 契約驅動測試,用於對兼容性做回歸測試。

服務架構的測試原則

微服務開發完成之後需要對其進行測試。微服務的測試包括單元測試、接口測試、集成測試和行為測試等,其中最重要的就是契約測試:
技術分享圖片

利用微服務框架提供的Mock機制,可以分別生成模擬消費者的客戶端測試樁和提供者的服務端測試樁,雙方可以基於Mock測試樁對微服務的接口契約進行測試,雙方都不需要等待對方功能代碼開發完成,實現了並行開發和測試,提高了微服務的構建效率。基於接口的契約測試還能快速的發現不兼容的接口變更,例如修改字段類型、刪除字段等。

服務架構的部署原則

測試完成之後,需要對微服務進行自動化部署。微服務的部署原則:獨立部署和生命周期管理、基礎設施自動化。需要有一套類似於CI/CD的流水線來做基礎設施自動化,具體可以參考Netflix開源的微服務持續交付流水線Spinnaker:
技術分享圖片

最後一起看下微服務的運行容器:微部署可以部署在Dorker容器、PaaS平臺(VM)或者物理機上。使用Docker部署微服務會帶來很多優先:

  • 一致的環境,線上線下環境一致。
  • 避免對特定雲基礎設施提供商的依賴。
  • 降低運維團隊負擔。
  • 高性能接近裸機性能。
  • 多租戶。

相比於傳統的物理機部署,微服務可以由PaaS平臺實現微服務自動化部署和生命周期管理。除了部署和運維自動化,微服務雲化之後還可以充分享受到更靈活的資源調度:

  • 雲的彈性和敏捷。
  • 雲的動態性和資源隔離。

服務架構的治理原則

服務部署上線之後,最重要的工作就是服務治理。微服務治理原則:線上治理、實時動態生效。
微服務常用的治理策略:

  • 流量控制:動態、靜態流控制。
  • 服務降級。
  • 超時控制。
  • 優先級調度。
  • 流量遷移。
  • 調用鏈跟蹤和分析。
  • 服務路由。
  • 服務上線審批、下線通知。
  • SLA策略控制。
  • 微服務治理模型如下所示:

技術分享圖片

最上層是為服務治理的UI界面,提供在線、配置化的治理界面供運維人員使用。SDK層是提供了微服務治理的各種接口,供服務治理Portal調用。最下面的就是被治理的微服務集群,集群各節點會監聽服務治理的操作去做實時刷新。例如:修改了流控閾值之後,服務治理服務會把新的流控的閾值刷到服務註冊中心,服務提供者和消費者監聽到閾值變更之後,獲取新的閾值並刷新到內存中,實現實時生效。由於目前服務治理策略數據量不是特別大,所以可以將服務治理的數據放到服務註冊中心(例如etcd/ZooKeeper),沒有必要再單獨做一套。

服務最佳實踐

介紹完微服務實施之後,下面我們一起學習下微服務的最佳實踐。
服務路由:本地短路策略。關鍵技術點:優先調用本JVM內部服務提供者,其次是相同主機或者VM的,最後是跨網絡調用。通過本地短路,可以避免遠程調用的網絡開銷,降低服務調用時延、提升成功率。原理如下所示:

技術分享圖片
服務調用方式:同步調用、異步調用、並行調用。一次服務調用,通常就意味著會掛一個服務調用線程。采用異步調用,可以避免線程阻塞,提升系統的吞吐量和可靠性。但是在實際項目中異步調用也有一些缺點,導致使用不是特別廣泛:
需要寫異步回調邏輯,與傳統的接口調用使用方式不一致,開發難度大一些。
一些場景下需要緩存上下文信息,引入可靠性問題。
並行調用適用於多個服務調用沒有上下文依賴,邏輯上可以並行處理,類似JDK的Fork/Join, 並行服務調用涉及到同步轉異步、異步轉同步、結果匯聚等,技術實現難度較大,目前很多服務框架並不支持。采用並行服務調用,可以把傳統串行的服務調用優化成並行處理,能夠極大的縮短服務調用時延。

微服務故障隔離:線程級、進程級、容器級、VM級、物理機級等。關鍵技術點:

  • 支持服務部署到不同線程/線程池中。
  • 核心服務和非核心服務隔離部署。
  • 為了防止線程膨脹,支持共享和獨占兩種線程池策略。

技術分享圖片

談到分布式,就繞不開事務一致性問題:大部分業務可以通過最終一致性來解決,極少部分需要采用強一致性。
技術分享圖片
具體的策略如下:

  • 最終一致性,可以基於消息中間件實現。
  • 強一致性,使用TCC框架。服務框架本身不會直接提供“分布式事務”,往往根據實際需要遷入分布式事務框架來支持分布式事務。

微服務的性能三要素:

  • I/O模型,這個通常會選用非堵塞的,Java裏面可能用java原生的。
  • 線程調度模型。
  • 序列化方式。

公司內部服務化,對性能要求較高的場景,建議使用異步非阻塞I/O(Netty) + 二進制序列化(Thrift壓縮二進制等) + Reactor線程調度模型。
技術分享圖片
最後我們一起看下微服務的接口兼容性原則:技術保障、管理協同。

  • 制定並嚴格執行《微服務前向兼容性規範》,避免發生不兼容修改或者私自修改不通知周邊的情況。
  • 接口兼容性技術保障:例如Thrift的IDL,支持新增、修改和刪除字段、字段定義位置無關性,碼流支持亂序等。
  • 持續交付流水線的每日構建和契約化驅動測試,能夠快速識別和發現不兼容。

現在流行的RPC框架:

服務治理型

  • dubbo
  • dubbox
  • motan

多語言型

  • grpc
  • thrift
  • avro
  • Protocol Buffers (google)
    技術分享圖片

上圖來自於dubbo。服務治理型RPC框架結構大多如此,大致分為服務提供者,服務消費者,註冊中心,監控報警中心幾大模塊。

服務性能

在服務化,或者微服務化過程中,首先考慮的問題就是性能問題,因為在服務化之後,會增加以下額外的性能開銷:

  1. 客戶端需要對消息進行序列化,主要占用CPU計算資源。
  2. 序列化時需要創建二進制數組,耗費JVM堆內存或者堆外內存。
  3. 客戶端需要將序列化之後的二進制數組發送給服務端,占用網絡帶寬資源。
  4. 服務端讀取到碼流之後,需要將請求數據報反序列化成請求對象,占用CPU計算資源。
  5. 服務端通過反射的方式調用服務提供者實現類,反射本身對性能影響就比較大。
  6. 服務端將響應結果序列化,占用CPU計算資源。
  7. 服務端將應答碼流發送給客戶端,占用網絡帶寬資源。
  8. 客戶端讀取應答碼流,反序列化成響應消息,占用CPU資源。

RPC框架高性能設計

要想提高效率,除了硬件的提升,主要考慮以下三個方面:

  1. I/O調度模型:同步阻塞I/O(BIO)還是非阻塞I/O(NIO)。
  2. 序列化框架的選擇:文本協議、二進制協議或壓縮二進制協議。
  3. 線程調度模型:串行調度還是並行調度,鎖競爭還是無鎖化算法。

IO調度現在主流的就是netty。
高性能序列化目前性能最好的是ice,google 的 pb協議,FB的thrift協議等
線程沒啥好說的,肯定多線程了。當然也可以是AKKA(java)

總結

綜上所述,服務化是現在大型互聯網公司主流的架構模式,現在還有更流行的微服務,docker部署等等。

個人建議采用dubbox,集成其他各種協議,在該系列文章最後有各個協議的性能對比。

之所以建議采用dubbox是因為,dubbox有比價完善的服務治理模型,其包含ZK註冊中心,服務監控等,可以很方便的為我們服務。
雖然dubbo本身不支持多語言,但是我們可以集成其他的序列化協議,比如thrift、avro,使其可以支持多種入門語言,讓部門間的協作溝通更加靈活方便

當然,在實際使用過程中,尤其是集成其他協議的過程中,肯定需要對協議本身有比較深入的了解,才能正確的使用。

motan

新浪微博開源的RPC框架

helloword示例直接去官網下載運行即可

github地址:https://github.com/weibocom/motan
文檔地址:https://github.com/weibocom/motan/wiki/zh_quickstart
用戶指南;https://github.com/weibocom/motan/wiki/zh_userguide

# grpc

中文版官方文檔:gRPC 官方文檔中文版

helloWord示例,我就是根據這個文章做的,寫得挺詳細的:rpc框架之gRPC 學習 - hello world

grpc原理: grpc原理分析

dubbo

dubbo 已經與12年年底停止維護升級,忽略

thrift

請參考我寫的另一篇文章:thrift學習筆記(一) thrift簡介及第一個helloword程序

dubbox

dubbox 是當當團隊基於dubbo升級的一個版本。是一個分布式的服務架構,可直接用於生產環境作為SOA服務框架。
dubbo官網首頁:http://dubbo.io/ 上面有詳細的用戶指南和官方文檔,介紹的比較詳細,這裏不再贅述。

當當官方的github地址:https://github.com/dangdangdotcom/dubbox

升級為spring4.X(及其他依賴組件)版本dubbox的github的地
址:https://github.com/yjmyzz/dubbox。

參考資料【博客:菩提樹下的楊過 的文章寫得非常全面,介紹的已經非常詳細了】:
dubbox升級spring到4.x及添加log4j2支持
分布式服務框架 dubbo/dubbox 入門示例
dubbox 的各種管理和監管
dubbo/dubbox 增加原生thrift及avro支持

# 各個RPC框架性能比較

測試環境

jdk7
win7 64位
idea
個人筆記本配置:
技術分享圖片

person對象:

private int age;
private String name;
private boolean sex;
private int childrenCount;

測試數據,入參:

private int ageStart;
private int ageEnd;

返回值:

Person.PersonBuilder builder = Person.builder();
List<Person> list = new ArrayList<Person>();
for (short i = 0; i < 10; i++) {
    list.add(builder.age(i).childrenCount(i).name("test" + i).sex(true).build());
}
return list;

各協議測試使用的配置

  • grpc


rpc getPersonList (yjmyzz.grpc.study.dto.QueryParameter) returns (yjmyzz.grpc.study.dto.PersonList) {}

  • motan

<motan:basicService export="demoMotan:8002"
group="motan-demo-rpc" accessLog="false" shareChannel="true" module="motan-demo-rpc"
application="myMotanDemo" registry="registry" id="serviceBasicConfig"/>

  • dubbox
<dubbo:protocol name="dubbo" serialization="kryo" optimizer="com.alibaba.dubbo.demo.SerializationOptimizerImpl"/>

<dubbo:service interface="com.alibaba.dubbo.demo.person.PersonService" ref="personService" protocol="dubbo"/>
  • thrift

TNonblockingServer + TFramedTransport

測試結果


rgpc 100000 次NettyServer調用,耗時:53102毫秒,平均1883次/秒 【簡單grpc】
rgpc 100000 次NettyServer調用,耗時:52138毫秒,平均1917次/秒 【簡單grpc】
rgpc 100000 次NettyServer調用,耗時:51800毫秒,平均1930次/秒 【簡單grpc】
rgpc 100000 次NettyServer調用,耗時:51313毫秒,平均1948次/秒 【簡單grpc】

rgpc 100000 次NettyServer調用,耗時:56812毫秒,平均1760次/秒[2016-10-08 19:17:31] Dubbo service server started! 【dubbox.kryo】
rgpc 100000 次NettyServer調用,耗時:55133毫秒,平均1813次/秒[2016-10-08 19:18:42] Dubbo service server started!【dubbox.kryo】
rgpc 100000 次NettyServer調用,耗時:52280毫秒,平均1912次/秒[2016-10-08 19:20:01] Dubbo service server started! 【dubbox.kryo】

rgpc 100000 次NettyServer調用,耗時:44414毫秒,平均2251次/秒[2016-10-08 19:13:34] Dubbo service server started! 【dubbox.fst】
rgpc 100000 次NettyServer調用,耗時:44805毫秒,平均2231次/秒[2016-10-08 19:12:25] Dubbo service server started! 【dubbox.fst】
rgpc 100000 次NettyServer調用,耗時:46245毫秒,平均2162次/秒[2016-10-08 19:14:43] Dubbo service server started! 【dubbox.fst】

rgpc 100000 次NettyServer調用,耗時:12203毫秒,平均8194次/秒[2016-10-09 19:52:34] Dubbo service server started!【dubbox.thrift】
rgpc 100000 次NettyServer調用,耗時:14142毫秒,平均7071次/秒[2016-10-09 19:30:17] Dubbo service server started!【dubbox.thrift】
rgpc 100000 次NettyServer調用,耗時:13762毫秒,平均7266次/秒[2016-10-09 19:30:43] Dubbo service server started!【dubbox.thrift】


rgpc 100000 次NettyServer調用,耗時:44334毫秒,平均2255次/秒 【motan】
rgpc 100000 次NettyServer調用,耗時:37844毫秒,平均2642次/秒 【motan】
rgpc 100000 次NettyServer調用,耗時:39007毫秒,平均2563次/秒 【motan】
rgpc 100000 次NettyServer調用,耗時:38610毫秒,平均2590次/秒 【motan】

測試結果說明

使用的自己的筆記本電腦測試的,測試的方式可能不太專業,但能夠說明問題。
通過上面結果可以看到,thrift的性能最好,而且是相當的好

網上其他人做的測試

ice-dubbo-thrift-grpc性能測試對比
RPC框架的性能比較

總結

影響RPC性能的因素主要有:

  • 序列化性能
  • IO性能
  • 線程模式

序列化的話,肯定是Google的PB協議和thrift最好,IO和線程的話,先流行的性能比較好的都是采用多線程非阻塞IO。

grpc是Google出品,使用了PB協議,但是由於它出現的比較晚,還不怎麽成熟,而且采用http協議,非常適合現在的微服務,不過性能上差了許多,而且像服務治理與監控都需要額外的開發工作,所以放棄grpc。
thrift和grpc一樣,性能優越,但是開發難度相比較於dubbox和motan也是高了一點點,需要編寫proto文件(其實對於程序員來說這算不上難度)。像服務治理與監控也是需要額外的開發工作。
dubbo比較老了,直接棄用。
dubbox和後來的motan都比較適合我們的團隊。dubbox後來經過當當的開發,引入了rest風格的http協議,並且還支持kryo/fst/dubbo/thrift等其他協議,而且其他團隊也在使用dubbo,集成方便,服務治理監控功能齊全,所以最終采用dubbox。

其實我個人而言還是喜歡thrift,畢竟性嫩優越,在大型分布式系統中,哪怕一點點性能提升累計在一起也是很可觀的。不過再具體選型的過程中還要結合團隊目前的狀況和團隊其他開發人員的接受程度進行取舍。

服務化實戰之 dubbo、dubbox、motan、thrift、grpc等RPC框架比較及選型