目录
●回调函数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
今天,你学会了吗?