目錄
●回調函數Callback Function與倒數計數器CountDownLatch
●What & Why
RPC(Remote Procedure Call),通俗地說,就是在一臺計算機上調用另一臺計算機提供的服務。這裏的服務對應RPC中的P(Procedure),表現形式通常是API接口,或者說好比一個本地代碼工程中的一個函數。那爲什麼要用RPC呢?最主要的原因有兩點:1、符合低耦合、職責分離、可複用的開發原則,將不同的服務(模塊、功能……什麼名字都好,理解其本質即可)放在不同的代碼工程,甚至不同的計算機(服務器)上,避免所有代碼雜糅在一個工程中,難以開發與維護;2、緩解負載壓力,不同計算機(服務器)提供不同的服務,各司其職,不用做所有事,降低資源耗盡的風險。
RPC其實沒有那麼高深,如果不考慮底層實現原理,則對於程序員來說幾乎完全透明,就是一套固定步驟的開發流程,和調用本地工程代碼中的函數沒有差別。以筆者目前所做的項目爲例,步驟大體爲:準備對應的stub(筆者將其理解爲對方所能提供函數,在我方的一個說明)、準備遠程調用的通道、控制器對象;開啓子線程,利用回調函數Callback Function,等待處理響應,並用線程倒數控制器CountDownLatch讓主線程阻塞;開啓遠程調用。
●回調函數Callback Function與倒數計數器CountDownLatch
筆者之前看過一些關於回調函數的文章,概念上已經理解,即A類的函數a1調用B類的函數b1,b1中需要調用A類的函數a2,這個a2就是回調函數。但是對於其存在的意義,或者說使用回調函數的場景到底是什麼,還不太想得到。直到項目中接觸到了RPC,才真的體會到回調函數的作用。
我們做如下安排,服務器B是一臺提供許多實用功能/服務的機器,它對外提供的接口包括字符串處理(strDeal)等,我們在服務器A的代碼中,去調用服務器B所提供的函數。
服務器A——
public class Client{
public boolean checkBackMessage(BackMessage backMessage){
return backMessage.equals("Done") ? true : false;
}
public BackMessage dealStr(String str) {
//接收RPC響應消息的對象
final BackMessage backMessage = new BackMessage();
//構造一個RPC通道,具體代碼未貼出,根據不同框架、項目而異
RpcChannelImpl channel = RpcChannelImpl.builderChannel();
if (channel != null) {
//構造一個RPC控制器
RpcController controller = channel.newRpcController();
//對方所能提供的服務/函數都通過stub進行描述
Service.Stub stub = Service.newStub(channel);
//構造RPC的請求
DealStrRequest.Builder request = DealStrRequest.newBuilder();
//設置請求參數
request.setCmd(CmdUtil.newRequestCmd(Hpp.CmdId.SERVER_RESTART_REQ));
request.setStr(str);
//倒數計數器
final CountDownLatch latch = new CountDownLatch(1);
//回調函數處理響應
RpcCallback<DealStrResponse> done = new RpcCallback<DealStrResponse>() {
@Override
public void run(DealStrResponse response) {
try {
//響應處理
backMessage.setBackCode(response.getCmd().getResultCode());
backMessage.setBackMessage(response.getCmd().getResultString());
System.out.println("完成RPC調用,接收到響應,已進行設置")
} catch (Exception e) {
latch.countDown();
}
latch.countDown();
}
};
//執行RPC調用
stub.dealStr(controller, request.build(), done);
//主線程阻塞等待響應處理完成
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
backMessage.setBackCode(-1);
}
return backMessage;
}
}
在服務器A中,我們需要準備一些對象,他們包括接收響應對象backMessage、RPC通道channel、PRC控制器controller、對方服務存根stub、RPC請求request。通過調用存根裏的函數接口就和調用本地的函數一樣。
我們對於響應的處理採用了比較精妙的方式:開啓一個子線程,定義回調函數run(),並且用CountDownLatch去阻塞主線程,讓其等待子線程中回調函數完成處理再繼續進行。
我們繼續看看服務器B所提供的RPC的服務——
public class ServiceImpl extends Service{
@HppMethod(commonId = 10001)
public void dealStr(RpcController controller, DealStrRequest request, RpcCallback<DealStrResponse> done) {
//對請求中的字符串進行大寫轉換並存入數據庫
request.getStr.toUpperCase().save();
int ret=ErrorCode.CMS_SUCCESSED;
DealStrResponse.Builder response = DealStrResponse.newBuilder().setCmd(CmdUtil.newResponseCmd(request.getCmd(), ResultUtil.getResultCode(ret)));
//回調函數!!
done.run(response.build());
}
//提供的其他RPC服務/函數
@HppMethod(commonId = 10002)
public void method1(RpcController controller, method1tRequest request, RpcCallback<method1tResponse> done) {
……
}
@HppMethod(commonId = 10003)
public void method2(RpcController controller, method2tRequest request, RpcCallback<method1tResponse> done) {
……
}
……
}
在其處理完字符串後,回調了服務器A的函數,即done.run(response.build()),利用回調函數,把響應作爲參數從服務器B傳給服務器A中,並在服務器A的回調函數中對響應進行處理(設置響應碼、響應消息、打印日誌等操作)。
整個過程的數據流向就非常明晰了:服務器A的字符串通過請求發給服務器B(走RPC調用),服務器B處理完請求,生成響應對象,利用回調函數將其交給服務器A處理。怎麼樣,是不是似曾相識,好像在哪兒聽過?Bingo!沒錯!Ajax裏面也用到了回調函數!頁面前端發送請求(例如Post)給後端處理,後端生成響應回傳給頁面,Ajax異步處理響應生成頁面內容。
function testAjax(){
$.ajax({
dataType:"text",
type:"POST",
cache:false,
url: path+"/testAjax.action",
data:{
"textDetail" : str
},
success:function(response){
if(response != null){
consolo.log(response);
}
},
error:function(response){
tLayer.warning("Error!");
}
});
}
回到正題,我們再看看服務器A中的回調函數裏面用到的CountDownLatch,這也是一個非常精妙的設計,它的作用是通過倒數來控制線程的阻塞。我們設置倒數計數爲1,當回調函數所在線程完成了響應處理,則將其減1,而主線程則在latch.await()的地方等待CountDownLatch倒數變爲0才繼續進行。可見,CountDownLatch適用於主線程中等待多個線程均執行完成後才繼續進行的場景。
筆者目前接觸的項目自己搭建的RPC架構,實際開發中,大家也可以選擇Thrift、Dubbo等第三方的框架進行使用。目前很火的微服務架構,就用到了RPC的相關思想,這一部分的知識還是值得掌握的。最後,給大家推薦兩篇文章:
https://blog.csdn.net/mindfloating/article/details/39473807
https://blog.csdn.net/mindfloating/article/details/39474123
今天,你學會了嗎?