Motan框架 - 輕量級 RPC 框架
基本介紹
Motan是一套基于java開發的RPC框架,除了常規的點對點調用外,motan還提供服務治理功能,包括服務節點的自動發現、摘除、高可用和負載均衡等。Motan具有良好的擴展性,主要模塊都提供了多種不同的實現,例如支持多種注冊中心,支持多種rpc協議等。
架構概述
Motan中分為服務提供方(RPC Server),服務調用方(RPC Client)和服務注冊中心(Registry)三個角色。
- Server提供服務,向Registry注冊自身服務,并向注冊中心定期發送心跳匯報狀態;
- Client使用服務,需要向注冊中心訂閱RPC服務,Client根據Registry返回的服務列表,與具體的Sever建立連接,并進行RPC調用。
- 當Server發生變更時,Registry會同步變更,Client感知后會對本地的服務列表作相應調整。
三者的交互關系如下圖:
模塊概述
Motan框架中主要有register、transport、serialize、protocol幾個功能模塊,各個功能模塊都支持通過SPI進行擴展,各模塊的交互如下圖所示:
register
用來和注冊中心進行交互,包括注冊服務、訂閱服務、服務變更通知、服務心跳發送等功能;Server端會在系統初始化時通過register模塊注冊服務,Client端在系統初始化時會通過register模塊訂閱到具體提供服務的Server列表,當Server 列表發生變更時也由register模塊通知Client。
protocol
用來進行RPC服務的描述和RPC服務的配置管理,這一層還可以添加不同功能的filter用來完成統計、并發限制等功能。
serialize
將RPC請求中的參數、結果等對象進行序列化與反序列化,即進行對象與字節流的互相轉換;默認使用對java更友好的hessian2進行序列化。
transport
用來進行遠程通信,默認使用Netty nio的TCP長鏈接方式。
cluster
Client端使用的模塊,cluster是一組可用的Server在邏輯上的封裝,包含若干可以提供RPC服務的Server,實際請求時會根據不同的高可用與負載均衡策略選擇一個可用的Server發起遠程調用。
在進行RPC請求時,Client通過代理機制調用cluster模塊,cluster根據配置的HA和LoadBalance選出一個可用的Server,通過serialize模塊把RPC請求轉換為字節流,然后通過transport模塊發送到Server端。
配置概述
Motan框架中將功能模塊抽象為四個可配置的元素,分別為:
-
protocol:服務通信協議。服務提供方與消費方進行遠程調用的協議,默認為motan協議,使用hessian2進行序列化,netty作為Endpoint以及使用motan自定義的協議編碼方式。
-
registry:注冊中心。服務提供方將服務信息(包含ip、端口、服務策略等信息)注冊到注冊中心,服務消費方通過注冊中心發現服務。當服務發生變更,注冊中心負責通知各個消費方。
-
service:服務提供方提供的服務。使用方將核心業務抽取出來,作為獨立的服務。通過暴露服務并將服務注冊至注冊中心,從而使調用方調用。
-
referer:服務消費方對服務的引用,即服務調用方。
Motan推薦使用spring配置rpc服務,目前Motan擴展了6個自定義Spring xml標簽:
- motan:protocol
- motan:registry
- motan:basicService
- motan:service
- motan:basicReferer
- motan:referer
每種標簽的詳細含義請參考后文配置說明部分。全部參數清單請參考配置清單。
使用Motan
Motan主要使用Spring進行配置,業務代碼無需修改。關于在項目中使用Motan框架的具體步驟,請參考:快速入門。
在使用Motan框架時,除了配置之外還需要注意工程依賴及Motan框架本身的異常處理。
工程依賴
Motan框架采用模塊化設計,使用時可以按需依賴。目前的模塊有:
- motan-core
Motan核心框架 - motan-transport-netty
基于Netty協議的長連接傳輸協議 - motan-registry-consul
Consul服務發現組件 - motan-registry-zookeeper
Zookeeper服務發現組件 - motan-springsupport
Spring標簽解析相關功能
處理調用異常
-
業務代碼異常
當調用的遠程服務出現異常時,Motan會把Server業務中的異常對象拋出到Client代碼中,與本地調用邏輯一致。注意:如果業務代碼中拋出的異常類型為Error而非Exception(如OutOfMemoryError),Motan框架不會直接拋出Error,而是拋出包裝了Error的MotanServiceException異常。
-
MotanServiceException
使用Motan框架將一個本地調用改為RPC調用后,如果出現網絡問題或服務端集群異常等情況,Motan會在Client調用遠程服務時拋出MotanServiceException異常,業務方需要自行決定后續處理邏輯。 -
MotanFrameworkException
框架異常,比如系統啟動、關閉、服務暴露、服務注冊等非請求情況下出現問題,Motan會拋出此類異常。
配置說明
協議與連接(motan:protocol)
介紹
Protocol用來配置Motan服務的協議。不同的服務適用不同的協議進行傳輸,可以自行擴展協議。
motan協議
<motan:protocol name="motan" />
Motan默認的rpc協議為motan協議,使用tcp長連接模式,基于netty通信。
負載均衡
Motan 在集群負載均衡時,提供了多種方案,缺省為 ActiveWeight,并支持自定義擴展。 負載均衡策略在Client端生效,因此需在Client端添加配置
目前支持的負載均衡策略有:
-
ActiveWeight(缺省)
<motan:protocol ... loadbalance="activeWeight"/>
低并發度優先: referer 的某時刻的 call 數越小優先級越高
由于 Referer List 可能很多,比如上百臺,如果每次都要從這上百個 Referer 或者最低并發的幾個,性能有些損耗,因此 random.nextInt(list.size()) 獲取一個起始的 index,然后獲取最多不超過 MAX_REFERER_COUNT 的狀態是 isAvailable 的 referer 進行判斷 activeCount. -
Random
<motan:protocol ... loadbalance="random"/>
隨機,按權重設置隨機概率。
在一個截面上碰撞的概率高,但調用量越大分布越均勻,而且按概率使用權重后也比較均勻,有利于動態調整提供者權重。 -
RoundRobin
<motan:protocol ... loadbalance="roundrobin"/>
輪循,按公約后的權重設置輪循比率
-
LocalFirst
<motan:protocol ... loadbalance="localFirst"/>
本地服務優先獲取策略,對referers根據ip順序查找本地服務,多存在多個本地服務,獲取Active最小的本地服務進行服務。
當不存在本地服務,但是存在遠程RPC服務,則根據ActivWeight獲取遠程RPC服務
當兩者都存在,所有本地服務都應優先于遠程服務,本地RPC服務與遠程RPC服務內部則根據ActiveWeight進行 -
Consistent
<motan:protocol ... loadbalance="consistent"/>
一致性 Hash,相同參數的請求總是發到同一提供者
-
ConfigurableWeight
<motan:protocol ... loadbalance="configurableWeight"/>
權重可配置的負載均衡策略
容錯策略
Motan 在集群調用失敗時,提供了兩種容錯方案,并支持自定義擴展。 高可用集群容錯策略在Client端生效,因此需在Client端添加配置 目前支持的集群容錯策略有:
-
Failover 失效切換(缺省)
<motan:protocol ... haStrategy="failover"/>
失敗自動切換,當出現失敗,重試其它服務器。
-
Failfast 快速失敗
<motan:protocol ... haStrategy="failfast"/>
快速失敗,只發起一次調用,失敗立即報錯。
連接控制
-
限制服務端連接池工作線程數
<motan:protocol id="demoMotan" name="motan" maxWorkerThread="800" minWorkerThread="20"/>
-
限制客戶端對每個服務建立的連接數
<motan:protocol name="motan" maxClientConnection="10" minClientConnection="2"/>
本地調用
<motan:protocol name="injvm" />
Injvm 協議是一個偽協議,它不開啟端口,不發起遠程調用,只在 JVM 內直接關聯,但執行 Motan 的 Filter 鏈。
注冊中心與服務發現(motan:registry)
介紹
注冊中心配置。用于配置注冊中心的注冊協議、地址端口、超時時間等。motan:registry包含以下常用屬性:
- name:標識配置名稱
- regProtocol:標識注冊中心協議
- address:標識注冊中心地址
Motan支持使用多種Registry模塊,使用不同注冊中心需要依賴對應jar包。
使用Consul作為注冊中心
<motan:registry regProtocol="consul" name="my_consul" address="consul_port:port"/>
使用Zookeeper作為注冊中心
zookeeper為單節點
```xml
<motan:registry regProtocol="zookeeper" name="my_zookeeper" address="zookeeper_ip1:port"/>
zookeeper多節點集群
```xml
<motan:registry regProtocol="zookeeper" name="my_zookeeper" address="zookeeper_ip1:port1,zookeeper_ip2:port2,zookeeper_ip3:port"/>
</code></pre>
不使用注冊中心
在開發及測試環境下,經常需要繞過注冊中心,只測試指定服務提供者,這時候可能需要 點對點直連,點對點直聯方式,將以服務接口為單位,忽略注冊中心的提供者列表,需要在配置motan:referer時定義directUrl屬性:
<motan:referer id="xxxService" interface="com.motan.xxx.XxxService" directUrl="server_ip:server_port" />
服務提供方(motan:service)
介紹
定義提供給外部調用的接口,motan:service包含以下常用屬性:
- interface:標識服務的接口類名
- ref:標識服務的實現類,引用具體的spring業務實現對象
- export:標識服務的暴露方式,格式為“protocolId:port”(使用的協議及對外提供的端口號),其中protocolId:應與motan:protocol中的name一致
- group:標識服務的分組
- module:標識模塊信息
- protocol:標識service使用的協議,與motan:protocol中的name對應,默認為motan協議
- basicService:標識使用的基本配置,引用motan:basicService對象
Motan在注冊中心的服務是以group的形式保存的,一般推薦一個分組以機房+業務線進行命名,如yf-user-rpc。一個分組中包含若干的Service,一個Service即是java中的一個接口類名,每個Service下有一組能夠提供對應服務的Server。
使用basicService簡化配置
<motan:basicService .../>
rpc服務的通用配置,用于配置所有服務接口的公共配置,減少配置冗余。basicService包含以下常用屬性:
- id:標識配置項
- export:標識服務的暴露方式,格式為“protocolId:port”(使用的協議及對外提供的端口號),其中protocolId:應與motan:protocol中的name對應
- group:標識服務的分組
- module:標識模塊信息
- protocol:標識service使用的協議,與motan:protocol中的name對應,默認為motan協議
- registry:標識service使用的注冊中心,與motan:registry中的name對應
motan:service可以通過以下方式引用基本配置。
<!-- 通用配置,多個rpc服務使用相同的基礎配置. group和module定義具體的服務池。export格式為“protocol id:提供服務的端口” -->
<motan:basicService id="serviceBasicConfig" export="demoMotan:8002" group="motan-demo-rpc" module="motan-demo-rpc" registry="registry" protocol="motan"/>
<!-- 通用配置,多個rpc服務使用相同的基礎配置. group和module定義具體的服務池。export格式為“protocol id:提供服務的端口” -->
<motan:service interface="com.weibo.motan.demo.service.MotanDemoService"
ref="demoServiceImpl" basicService="serviceBasicConfig"/>
motan:service中的basicService屬性用來標識引用哪個motan:basicService對象,對于basicService中已定義的內容,service不必重復配置。
服務調用方(motan:referer)
介紹
調用方對象,motan:referer包含以下常用屬性:
- id:標識配置項
- group:標識服務的分組
- module:標識模塊信息
- protocol:標識referer使用的協議,與motan:protocol中的name對應,默認為motan協議
- registry:標識referer使用的注冊中心,與motan:registry中的name對應
- basicReferer:標識使用的基本配置,引用motan:basicReferer對象
Client端訂閱Service后,會從Registry中得到能夠提供對應Service的一組Server,Client把這一組Server看作一個提供服務的cluster。當cluster中的Server發生變更時,Client端的register模塊會通知Client進行更新。

使用basicReferer簡化配置
調用方基礎配置。用于配置所有服務代理的公共屬性。
- id:標識配置項
- group:標識服務的分組
- module:標識模塊信息
- protocol:標識referer使用的協議,與motan:protocol中的name對應,默認為motan協議
- registry:標識referer使用的注冊中心,與motan:registry中的name對應
motan:referer可以通過以下方式引用基本配置。
<!-- 通用referer基礎配置 -->
<motan:basicReferer id="clientBasicConfig" group="motan-demo-rpc" module="motan-demo-rpc" registry="registry" protocol="motan"/>
<!-- 具體referer配置。使用方通過beanid使用服務接口類 -->
<motan:referer id="demoReferer" interface="com.weibo.motan.demo.service.MotanDemoService" basicReferer="clientBasicConfig"/>
</code></pre>
motan:referer中的basicService屬性用來標識引用哪個motan:basicReferer對象,對于basicReferer中已定義的內容,service不必重復配置。
配置清單
詳細內容請參考配置清單
運維及監控
優雅的停止服務
Motan支持在Consul集群環境下優雅的關閉節點,當需要關閉或重啟節點時,可以先將待上線節點從集群中摘除,避免直接關閉影響正常請求。
待關閉節點需要調用以下代碼,建議通過servlet或業務的管理模塊進行該調用。
MotanSwitcherUtil.setSwitcher(ConsulConstants.NAMING_PROCESS_HEARTBEAT_SWITCHER, false)
注意:Zookeeper模塊此功能正在開發。
管理后臺
管理后臺主要包括RPC服務查詢、流量切換、Motan指令設置等功能,需使用ZooKeeper作為注冊中心
管理后臺獨立于Motan其他部分,可單獨部署
管理后臺安裝
-
配置:
修改配置文件config.properties,配置ZooKeeper的registry地址,默認不使用數據庫
默認的登錄用戶及權限如下: 管理員:用戶名admin 密碼admin 訪客:用戶名guest 密碼guest
若需使用歷史操作查詢功能,則需配置數據庫: 數據庫表結構位于motan-manager.sql,可直接導入 數據庫配置地址位于config.properties 在WEB-INF/web.xml的contextConfigLocation中添加classpath*:spring-mybaits.xml
-
啟動
在motan-open/motan-manager/下執行mvn install 將motan-open/motan-manager/target/motan-manager.war部署到任意web容器中(如:tomcat的webapps目錄下),運行web容器即可
管理后臺使用
Coming Soon...
日志說明
Motan會打印兩種類型的日志,幫助運維人員監控系統狀態。
請求類日志
通過motan:service或motan:referer的accessLog屬性來配置,基本格式如下:
"accesslog" - date - side - local_application_module - localip - interface - method_name - parameter_name - to_ip - remote_application_module - result - request_id - process_time_mills (分隔符為"|")
統計類日志
所有請求的統計:
[motan-totalAccessStatistic] total_count: 32565 slow_count: 26 biz_excp: 0 other_excp: 2 avg_time: 1.93ms biz_time: 0.94ms avg_tps: 1085
total_count: 30s 內總請求數
slow_count:30s 內慢請求數(超過 50ms 算 slow)
biz_excp: 30s 內業務處理異常的總數
other_excp: 30s 其他異常的總數
avg_time: 所有接口的平均響應時間(網絡傳輸+序列化+service 端處理)
biz_time: 所有接口的 service 端的業務處理時間(不包含序列化和網絡傳輸)
avg_tps:平均 tps
注:上面是基于 client 端為維度的統計,service 端也有,其中 avg_time 便是業務處理時間,biz_time 為 0。
單方法的統計:
[motan-accessStatistic] item: injvm://cn.sina.api.data.service.GroupService.getGroupMemberCounters(long,long) total_count: 0 slow_count: 0 biz_excp: 0 other_excp: 0 avg_time: 0.00ms biz_time: 0.00ms avg_tps: 0 max_tps: 0 min_tps: 0
total_count: 30s 該接口的請求數,
slow_count: 30s 內該接口的慢請求數 (超過 50ms 的算 slow) ,
biz_excp: 30s 內該接口業務處理異常的總數,
other_excp: 30s 該接口其他異常的總數,
avg_time: 平均響應時間(網絡傳輸+序列化+service 端處理),
biz_time: service 端的業務處理時間(不包含序列化和網絡傳輸) ,
avg_tps:平均 tps,
max_tps: 最大的 TPS,
min_tps: 最小的 TPS
內存統計:
[motan-memoryStatistic] 1954.67MB of 7987.25 MB (24.5%) used
性能測試
Motan源碼中提供了性能測試框架,便于使用者進行性能評估,源碼請參考https://github.com/weibocom/motan/tree/master/motan-benchmark。
以下是我們測試的結果:
測試環境
硬件配置
Server端:
CPU:model name:Intel(R) Xeon(R) CPU E5-2620 v2 @ 2.10GHz,cache size: 15360 KB,processor_count : 24
內存:16G
網絡:千兆網卡
硬盤:300GB
Client端:
CPU:model name: Intel(R) Xeon(R) CPU E5-2620 v2 @ 2.10GHz,cache size:15360 KB,processor_count : 24
內存:16G
網絡:千兆網卡
硬盤:300GB
</code></pre>
軟件配置
JDK版本:
java version "1.7.0_75"
OpenJDK Runtime Environment (rhel-2.5.4.2.el7_0-x86_64 u75-b13)
OpenJDK 64-Bit Server VM (build 24.75-b04, mixed mode)
JVM參數:
java -Djava.net.preferIPv4Stack=true -server -Xms1g -Xmx1g -XX:PermSize=128m
</code></pre>
測試腳本
Server測試場景:
并發多個Client,連接數50,并發數100,測試Server極限性能
Client測試場景:
單客戶端,10連接,在并發數分別為1,10,20,50的情況下,分別進行如下場景測試:
- 傳入空包,不做任何處理,原樣返回
- 傳入Pojo嵌套對象,不做任何處理,原樣返回
- 傳入1kString,不做任何處理,原樣返回
- 傳入5kString,不做任何處理,原樣返回
- 傳入10kString,不做任何處理,原樣返回
- 傳入20kString,不做任何處理,原樣返回
- 傳入30kString,不做任何處理,原樣返回
- 傳入50kString,不做任何處理,原樣返回。
</code></pre>
測試結果
Server測試結果:
請求空包:單Server極限TPS:18W
請求1KString:單Server極限TPS:8.4W
請求5KString:單Server極限TPS:2W
Client測試結果:
對比圖:

原始數據:
并發數
測試場景
平均TPS
平均響應時間(ms)
1
Empty
5601
0.178
1
Pojo
3556
0.281
1
1KString
2657
0.376
1
5KString
1100
0.908
1
10KString
949
1.052
1
20KString
600
1.664
1
30KString
512
1.95
1
50KString
253
3.939
10
Empty
39181
0.255
10
Pojo
27314
0.365
10
1KString
19968
0.5
10
5KString
11236
0.889
10
10KString
5875
1.701
10
20KString
4493
2.224
10
30KString
3387
2.951
10
50KString
1499
6.668
20
Empty
69061
0.289
20
Pojo
47226
0.423
20
1KString
34754
0.575
20
5KString
18883
1.058
20
10KString
9032
2.214
20
20KString
5471
3.654
20
30KString
3724
5.368
20
50KString
1973
10.133
50
Empty
69474
0.719
50
Pojo
64022
0.78
50
1KString
58937
0.848
50
5KString
20703
2.414
50
10KString
10761
4.645
50
20KString
5614
8.904
50
30KString
3782
13.214
50
50KString
2285
21.869