賴歆
(福富軟件,福建 福州 350003)
遠程方法調用的實現方案研究
賴歆
(福富軟件,福建 福州 350003)
針對分布式系統需要通過網絡來表達調用的語義和傳達調用的數據而會遇到的問題,本文對此問題展開探討,首先對跨機間的遠程調用進行分析,其次從通訊、編碼解碼進行研究,最后了提出具體的解決方案。
遠程方法;通訊;序列化
分布式系統并不像單機系統可以直接通過本地調用,其基本上由大大小小的服務組成,并且各種服務通常會被部署在不同的機器上。由于各種服務不在同一個內存空間,因此不能直接調用,需要通過網絡來表達調用的語義和傳達調用的數據。這時就會遇到兩個問題:
(1)要搭建一個新服務,需要依賴遠程的服務,如何才能調用遠程服務?
(2)遠程訪問如何才能滿足高效的性能要求?下文我們將對這兩個問題展開探討。
由于各服務部署在不同機器,服務消費方調用一個服務就要編寫一堆網絡通信相關的代碼,不僅復雜而且極易出錯。
我們需要一種能像調用本地服務一樣調用遠程服務的方式,讓網絡通信中的細節(jié)對調用者透明,這樣將大大簡化工作,提高編碼效率,這種方式就是RPC(Remote Procedure Call Protocol)。RPC調用的流程如圖1所示:
圖1 RPC調用流程圖
RPC的目標是將這些步驟都封裝起來,讓整體的過程細節(jié)對使用者變得透明。
2.1 實現透明化遠程服務調用
通過JAVA的動態(tài)代理技術封裝通信細節(jié),讓用戶以類似本地調用的方式調用遠程服務。代理類通過調用被代理類的相關方法,提供預處理、過濾、事后處理等服務,可以使用JDK中的java.lang.reflect.Proxy類來實現動態(tài)代理。
下面簡單介紹用動態(tài)代理進行封裝的示例。調用者先從RPCProxyClient獲得服務提供方的接口,當執(zhí)行helloServ. hello()方法時會調用Invoke方法,在代理類的Invoke方法中封裝與遠端服務通信的細節(jié)。
/**定義接口并實現方法,它存在于遠端*/
public interface IHello{
void hello();
}
public class Hello implements IHello{
@Override
public String hello(){
System.out.println("Hello.");
return"Hello.";
}
}
/**通過動態(tài)代理封裝屏蔽通信細節(jié)*/
public class RPCProxyClient implements java.lang.reflect. InvocationHandler{
private Object obj;
public RPCProxyClient(Object obj){
this.obj=obj;
}
public static Object getProxy(Object obj){
return java.lang.reflect.Proxy.newProxyInstance(obj. getClass().getClassLoader(),
obj.getClass().getInterfaces(),new RPCProxy-Client(obj));
}
public Object invoke(Object proxy,Method method, Object[]args)
throws Throwable{
//結果參數;
Object result=new Object();
//...實現遠程間通信的相關邏輯
//...
return result;
}
}
/**調用示例*/
public class Test{
public static void main(String[]args){
IHello helloServ=(hello)RPCProxyClient.getProxy (IHello.class);
helloServ.hello();
}
}
2.2 實現消息的編碼解碼
在Invoke對通信細節(jié)進行封裝,第一步要確定客戶端和服務端相互通信的消息結構。一般需要包括幾個主要的信息:
(1)接口名稱:用于服務端確定調用哪個接口,如上面例子里接口名是“IHello”。
(2)方法名:用于服務端確定調用哪個方法(一個接口內可以有多個方法),上面例子里是“hello”。
(3)參數類型&參數值。
(4)唯一請求標識:遠程調用可能是同步的,也可能是異步的,當設計工作在異步方式時,需要唯一請求標識來明確具體的事務。
(5)返回值。
確定了消息的數據結構后,下一步要考慮序列化與反序列化,以便于將數據結構或對象轉換為二進制串后進行網絡傳輸,以及從網絡接收后將二進制轉換回對象進行后續(xù)業(yè)務邏輯處理。
序列化/反序列化的方案很多,每種序列化方案都有優(yōu)點和缺點,從遠程調用的角度上看,主要看三點:(1)通用性,比如是否能支持Map等復雜的數據結構;(2)性能,包括時間復雜度和空間復雜度;(3)可擴展性,如果序列化協議具有良好的可擴展性,支持自動增加新的業(yè)務字段,刪除老的字段,而不影響老的服務,這將大大提供系統的健壯性。
可以根據需要選擇互聯網廣泛使用的hessian、protobuf、avro等成熟的序列化解決方案來搭建。
2.3 實現網絡通信
消息數據結構被序列化為二進制串后,接下來要實現網絡通信。網絡通信的實現主要有以下幾種:
(1)同步阻塞(即早期的IO操作):在此種方式下,用戶進程在發(fā)起一個IO操作以后,必須等待IO操作的完成,只有當真正完成了IO操作以后,用戶進程才能運行。JAVA傳統的IO模型屬于此種方式,適用于連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,并發(fā)局限于應用中。
(2)同步非阻塞(NIO):在此種方式下,用戶進程發(fā)起一個IO操作以后邊可返回做其它事情,但是用戶進程需要定時去詢問IO操作是否就緒,從而引入不必要的CPU資源浪費。在JDK 1.4開始支持此種方式,適用于連接數目多且連接比較短(輕操作)的架構,并發(fā)局限于應用中。
(3)異步(AIO):此種方式是指應用發(fā)起一個IO操作以后,不等待內核IO操作的完成,等內核完成IO操作以后會通知應用程序。在JDK1.7開支持該方式,適用于連接數目多且連接比較長(重操作)的架構,充分調用OS參與并發(fā)操作。
接下來對幾種通信框架gRPC、Java RMI、Netty進行性能驗證,驗證環(huán)境的主要配置參數見表1:
表1 驗證環(huán)境的主要配置參數
驗證過程:
(1)編寫驗證程序在2臺主機間調用,1次調用算為1次事務。
(2)逐步加大線程數測試多線程并發(fā)的性能增長曲線,測試單線程處理多通道的性能增長能力,測試不同長度數據下的性能數據變化規(guī)律,統計發(fā)送時間和對應返回接收時間的時間差作為延遲時間。
(3)觀察運行穩(wěn)定后,收集不同場景下的性能數據。
表2 幾種通信架構的性能驗證
Netty集群極限性能較高,擴展能力較好,資源利用率較高,適用于異步處理大量通道并發(fā)的業(yè)務場景;gRPC在ProtoBuf序列化協議使用上受到一定的限制,采用http2的網絡利用率較低;RMI的擴展能力較差,通道復用能力較差,且跨語言調用能力較弱。
本文對遠程調用場景進行分析,通過實現調用透明化、編碼解碼、網絡通信形成遠程方法調用的方案,描述方案適用的場景。通過方案原型的測試驗證,更深入地說明方案的特點,實際使用中可根據不同的場景需要,分別采用不同的方案。
[1]周筱瑜,雷曉俊,陳芳.分布式系統中的通信方式:R PC與R M I [J].電腦與電信,2 0 12(3):3 6-3 9.
[2]劉緒崇,張悅,鐘情花.分布式多用戶遠程控制與密取系統設計與實現[J].警察技術,2 0 15(6):3 6-3 9.
The Realization of Remote Method Invocation
Lai Xin
(FFCS,Fuzhou 350003,Fujian)
In view of the problem that the distributed system needs to express the semantics of the invocation and communicate the data of the invocation through the network,this paper firstly analyzes the remote invocation across machines;and then researches on the communication,encoding and decoding;and finally puts forward the specific solutions.
remote method;communications;serialize
TP311.52
A
1008-6609(2016)11-0068-03
賴歆(19 79-),男,福建人,系統架構工程師,研究方向為電信信息化。