RPC功能介紹
RPC (Remote Procedure Call)的主要功能目標是讓構建分布式計算(應用)更容易盏求,在提供強大的遠程調(diào)用能力時不損失本地調(diào)用的語義簡潔性仙畦。 為實現(xiàn)該目標钳幅,RPC 框架需提供一種透明調(diào)用機制讓使用者不必顯式的區(qū)分本地調(diào)用和遠程調(diào)用。
RPC采用C/S模式催享。請求程序就是一個客戶機,而服務提供程序就是一個服務器哟绊。首先因妙,客戶機調(diào)用進程發(fā)送一個有進程參數(shù)的調(diào)用信息到服務進程,然后等待應答信息票髓。在服務器端攀涵,進程保持睡眠狀態(tài)直到調(diào)用信息的到達為止。當一個調(diào)用信息到達洽沟,服務器獲得進程參數(shù)以故,計算結果發(fā)送答復信息,然后等待下一個調(diào)用信息玲躯。最后据德,客戶端調(diào)用進程接收答復信息,獲得進程結果跷车,然后調(diào)用執(zhí)行繼續(xù)進行棘利。
RPC調(diào)用流程
1)服務消費方(client)調(diào)用以本地調(diào)用方式調(diào)用服務;
2)client stub接收到調(diào)用后負責將方法朽缴、參數(shù)等組裝成能夠進行網(wǎng)絡傳輸?shù)南Ⅲw善玫;
3)client stub找到服務地址,并將消息發(fā)送到服務端密强;
4)server stub收到消息后進行解碼茅郎;
5)server stub根據(jù)解碼結果調(diào)用本地的服務;
6)本地服務執(zhí)行并將結果返回給server stub或渤;
7)server stub將返回結果打包成消息并發(fā)送至消費方系冗;
8)client stub接收到消息,并進行解碼薪鹦;
9)服務消費方得到最終結果掌敬。
RPC的任務就是封裝2-8步驟惯豆,使調(diào)用過程對調(diào)用方透明。
代理
java使用代理的方式封裝通信細節(jié)奔害。代理類的invoke方法中封裝了與遠端服務通信的細節(jié)楷兽,調(diào)用方首先從RPCProxyClient獲得服務提供方的接口,當執(zhí)行helloWorldService.sayHello(“test”)方法時就會調(diào)用invoke方法华临。
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 RPCProxyClient(obj));
}
/**
* 調(diào)用此方法執(zhí)行
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//結果參數(shù);
Object result = new Object();
// ...執(zhí)行通信相關邏輯
// ...
return result;
}
}
public class Test {
public static void main(String[] args) {
HelloWorldService helloWorldService = (HelloWorldService)RPCProxyClient.getProxy(HelloWorldService.class);
helloWorldService.sayHello("test");
}
}
序列化
客戶端的請求消息結構一般包括以下內(nèi)容:
(1)接口名和方法名
(2)參數(shù)類型和參數(shù)值
(3)超時時間
(5)requestID
服務端返回的消息結構一般包括以下內(nèi)容:
(1)返回值
(2)狀態(tài)碼
(3)requestID
確定了消息的數(shù)據(jù)結構后芯杀,下一步就是要考慮序列化與反序列化了。序列化就是將數(shù)據(jù)結構或?qū)ο筠D換成二進制串的過程雅潭,也就是編碼的過程揭厚。反序列化就是將在序列化過程中所生成的二進制串轉換成數(shù)據(jù)結構或者對象的過程。
從RPC的角度來選擇序列化方案寻馏,主要考慮(1)通用性棋弥,是否能支持比較復雜的數(shù)據(jù)結構;(2)可擴展性诚欠,增加或刪除字段能不影響老的服務;(3)性能漾岳,從時間和空間兩個維度去衡量序列化效率和序列化后的字節(jié)長度轰绵。
目前國內(nèi)各大互聯(lián)網(wǎng)公司廣泛使用hessian、protobuf尼荆、thrift左腔、avro等成熟的序列化解決方案來搭建RPC框架。
通信
消息體被編碼之后是需要將編碼后的請求消息傳輸?shù)椒辗酵比濉5讓拥腞PC傳輸通道可以是TCP也可以是UDP液样。
如何保證返回結果的有序性
如果有多個線程同時進行遠程方法調(diào)用,這時建立在client server之間的socket連接上會有很多雙方發(fā)送的消息傳遞巧还,前后順序也可能是隨機的鞭莽,server處理完結果后,將結果消息發(fā)送給client麸祷,client收到很多消息澎怒,怎么知道哪個消息結果是原先哪個線程調(diào)用的?
線程A和線程B同時向client socket發(fā)送請求requestA和requestB阶牍,socket先后將requestB和requestA發(fā)送至server喷面,而server可能將responseA先返回,盡管requestA請求到達時間更晚走孽。我們需要一種機制保證responseA返回給ThreadA惧辈,responseB返回給ThreadB。
1)client線程每次通過socket調(diào)用遠程接口前磕瓷,生成一個唯一的ID盒齿,即requestID(requestID必需保證在一個Socket連接里面是唯一的),一般常常使用AtomicLong從0開始累計數(shù)字生成唯一ID;
2)將處理結果的回調(diào)對象callback县昂,存放到全局ConcurrentHashMap里面put(requestID, callback)肮柜;
3)當client線程發(fā)送消息后,緊接著執(zhí)行callback的get()方法試圖獲取遠程返回的結果倒彰。在get()內(nèi)部审洞,則使用synchronized獲取回調(diào)對象callback的鎖。先檢測是否已經(jīng)獲取到結果待讳,如果沒有芒澜,則調(diào)用callback的wait()方法,釋放callback上的鎖创淡,讓當前線程處于等待狀態(tài)痴晦。
4)服務端接收到請求并處理后,將response結果(此結果中包含了前面的requestID)發(fā)送給客戶端琳彩,客戶端socket連接上專門監(jiān)聽消息的線程收到消息誊酌,分析結果取到requestID,再從前面的ConcurrentHashMap里面get(requestID)露乏,從而找到callback對象碧浊。再用synchronized獲取callback上的鎖,將方法調(diào)用結果設置到callback對象里瘟仿,再調(diào)用callback.notifyAll()喚醒前面處于等待狀態(tài)的線程箱锐。