Github项目NettyRpc 阅读(Netty+同/异步通讯+多线程+AQS+CAS+volatile)
Github项目:https://github.com/luxiaoxun/NettyRpc
Fork: https://github.com/sw008/NettyRpc
此项目很适合学习多线程和Netty
RPC调用流程
大体思路:整个异步/同步通讯流程,适用于大多数同步/异步socket通讯场景。可以在此基础上拓展实现例如异步回调等多种使用功能。整个异步请求+响应过程,通过ConcurrentHashMap<id, RPCFuture>关联。
1 客户端线程A(out) 创建RPCFuture对象(此对象包含 请求ID,request对象,response对象 等信息),保存ConcurrentHashMap<id, RPCFuture>保存。然后把request对象(含请求ID)通过输出流发送给服务端。线程A继续向下执行其他代码(此时下面2-4异步执行),直到其调用RPCFuture.get()后,线程A挂起。
2 服务端IO线程(in) 输入流Handler,接收请求信息和Channel 转发到工作线程池。
3 服务端工作线程(out) 处理成功后通过Channel输出流返回响应对象(包含请求ID)给客户端。
4 客户端线程B(in) 通过输入流Handler,接收响应对象,通过返回的请求ID在ConcurrentHashMap<id, RPCFuture>中找到发送时创建的RPCFuture更新其相应信息,并更新其AQS的状态,release唤醒调用RPCFuture.get()而挂起的线程A。
(Callback:此步还可以拓展实现异步回调,与RPCFuture同理。即第一步发送时创建回调执行对象,保存到ConcurrentHashMap中,在此步找到此对象并执行,此时回调对象还可以获得request、response信息。也可设置一个单例回调对象,在返回输入流中执行其回调方法)
5 客户端线程A 被唤醒取得响应结果response,继续执行。
代码实现过程
1 客户端采用JDK动态代理创建ObjectProxy类代理对象,并与服务接口绑定。
2 客户端调用服务接口方法,触发动态代理对象的ObjectProxy.invoke()
3 客户端发送请求, ObjectProxy.invoke(Object proxy, Method method, Object[] args) 是JDK动态代理InvocationHandler接口的方法
3.1 通过method、args,生成RpcRequest类对象(其包含成员变量 requestId、className、 methodName、parameterTypes、parameters)
3.2 ConnectManage.getInstance().chooseHandler() :RpcClientHandler 一个简单的负载均衡方法,找到应该调用的服务器。因为Netty客服端主机与服务端主机是通过一条Channel链接,每一条Channel代表一个服务端主机。每个RpcClientHandler中包含一个Channel链接服务端,一个ConcurrentHashMap<String, RPCFuture>记录请求ID和其对应的请求
3.3 RpcClientHandler.sendRequest(RpcRequest request) 将请求对象发送给服务端主机,等待对方接收成功后,返回RPCFuture对象实现异步调用。
RpcClientHandler类
ConcurrentHashMap<String, RPCFuture> pendingRPC;//保存 请求ID+对应RPCFuture
public RPCFuture sendRequest(RpcRequest request) {
final CountDownLatch latch = new CountDownLatch(1);
//创建自定义异步请求类RPCFuture对象
RPCFuture rpcFuture = new RPCFuture(request);
//pendingRPC为ConcurrentHashMap<String, RPCFuture> 记录请求ID和对应异步请求
//对方服务器通过channel返回Response对象时,本机输入流方法 通过pendingRPC+请求ID更新对应RPCFuture状态
pendingRPC.put(request.getRequestId(), rpcFuture);
//发送请求RpcRequest,并添加对方接收成功的异步监听对象,回调对象ChannelFutureListener
channel.writeAndFlush(request).addListener(
new ChannelFutureListener() { //实例化 一个匿名局部内部类对象
//一个异步监听对象 ,监听线程回调由Netty框架实现
//服务端接收到后 回调此匿名内部类对象 的方法 (注意不是对方处理完回调)
@Override
public void operationComplete(ChannelFuture future) {
//此处使用局部内部类的闭包特性,此局部内部类对象可调用此方法的局部变量latch
//对方接受成功,通过CountDownLatch唤醒当前线程
latch.countDown();
}
});
try {
//当前线程挂起 等待接收监听线程回调唤醒
latch.await();
} catch (InterruptedException e) {
logger.error(e.getMessage());
}
//先返回RPCFuture,此时只代表请求送达,但是对方服务器可能还没有处理完成
return rpcFuture;
}
4 服务端接收处理信息
服务端RpcHandler类继承Netty的SimpleChannelInboundHandler并实现channelRead0()方法,接收客户端信息,并通过反射执行。
可以看到消费者Netty input Handler收到消息之后直接把 请求信息+请求Channel 交给工作线程池。由工作线程处理请求,再通过请求Channel返回信息。即服务端IO线程只负责接收信息转发给工作线程,所有处理又工作线程池异步完成并返回结果。
RpcHandler类
public void channelRead0(final ChannelHandlerContext ctx,final RpcRequest request) throws Exception {
//接到信息后,直接提交到RpcServer中的线程池执行
RpcServer.submit(new Runnable() {
//同样用到了局部内部类的闭包特性,可以调用当前方法局部变量
@Override
public void run() {
RpcResponse response = new RpcResponse();
//实例化RpcResponse 并装配信息
response.setRequestId(request.getRequestId());
try {
Object result = handle(request);
response.setResult(result);
} catch (Throwable t) {
response.setError(t.toString());
}
//发送response到客户端
ctx.writeAndFlush(response).addListener(new ChannelFutureListener() {
//添加异步监听对象,发送成功后回调此对象方法
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
logger.debug("Send response for request " + request.getRequestId());
}
});
}
});
}
5 客户端接收响应信息
RpcClientHandler类继承Netty的SimpleChannelInboundHandler并实现channelRead0方法,接收服务端响应信息。
可以发现客户端发送请求和接收响应的方法都是RpcClientHandler类实现,因为发送和接收需要依靠同一个pendingRPC进行结果匹配,发送时将RPCFuture放入其中,接收响应后通过请求ID更新对应RPCFuture。
RpcClientHandler类
//客户端 收到响应信息
ConcurrentHashMap<String, RPCFuture> pendingRPC;
@Override
public void channelRead0(ChannelHandlerContext ctx, RpcResponse response) throws Exception {
//用过请求ID 在pendingRPC中找到发送时保存的RPCFuture
String requestId = response.getRequestId();
//pendingRPC保存了发送时的RPCFuture
RPCFuture rpcFuture = pendingRPC.get(requestId);
if (rpcFuture != null) {
pendingRPC.remove(requestId);
//更新对应rpcFuture,并且唤醒已经执行rpcFuture.get()的所有线程
rpcFuture.done(response);
}
}
6 RPCFuture类实现了Future接口,并通过AQS实现线程的挂起与唤醒。
方法调用线程 持有一个RPCFuture对象,并通过此对象get()挂起等待被其它线程唤醒。
IO输入流线程 收到响应结果后,Map中找到对应RPCFuture对象唤醒被挂起线程。
某个线程挂起后,只能等待其它线程通过将他挂起的Object来唤醒他。(此处是通过RPCFuture对象来挂起和唤醒)
sync对象实现了AbstractQueuedSynchronizer的tryRelease,tryAcquire方法。
当执行rpcFuture.done(response)时,将AQS中volatile int state通过CAS设置为1,唤醒已经执行rpcFuture.get()的所有线程。
拓展:可以通过CountDownLatch实现Future接口。
RPCFuture类
//5中,接收到服务端响应后执行的方法rpcFuture.done(response);
public void done(RpcResponse reponse) {
this.response = reponse;
//sync为AQS对象,通过CAS更新AQS中的状态值volatile int state;
sync.release(1);
invokeCallbacks();
// Threshold
long responseTime = System.currentTimeMillis() - startTime;
if (responseTime > this.responseTimeThreshold) {
logger.warn("Service response time is too slow. Request id = " + reponse.getRequestId() + ". Response Time = " + responseTime + "ms");
}
}
当前程执行rpcFuture.get()时,判断AQS中的volatile int state=1 ?,若还没有响应信息则当前线程进入挂起状态。
RPCFuture类
@Override
public Object get() throws InterruptedException, ExecutionException {
//AQS中的状态值volatile int state,判断对方服务器时候已经响应;
sync.acquire(-1);
if (this.response != null) {
return this.response.getResult();
} else {
return null;
}
}
6 Sync类,是RPCFuture的静态内部类。通过CAS控制volatile int state=1,决定调用线程是否需要挂起。volatile保证了可见性, CAS保证了原子性,整个过程是线程安全。使比较+赋值成为一个原子性操作,不会被其他线程打扰。可以把CAS理解成多线程的串行执行,再加上volatile的可见性有序性保障,所以是线程安全的。
AQS对象.acquire:请求资源,tryAcquire==false时挂起线程
AQS对象.release:释放资源,tryRelease==true时唤醒一个挂起线程
http://www.cnblogs.com/waterystone/p/4920797.html
static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1L;
//future 状态常量
private final int done = 1; //已完成
private final int pending = 0; //未完成
@Override
//获取资源
protected boolean tryAcquire(int arg) {
//判断当前 volatile int state=1
//返回false时,当前线程挂起
return getState() == done;
}
@Override
//释放资源
protected boolean tryRelease(int arg) {
if (getState() == pending) {
//CAS设置 volatile int state=1
//CAS保证操作原子性,线程安全
if (compareAndSetState(pending, done)) {
//因为只有发送线程会执行其请求对应的RPCFuture的get方法,所以只会有一个线程挂起等待
//返回true时,AQS框架会唤醒第一个等待线程
return true;
} else {
return false;
}
} else {
return true;
}
}
public boolean isDone() {
getState();
return getState() == done;
}
}