Github项目NettyRpc 阅读(Netty+多线程+AQS+CAS+volatile)

Github项目:https://github.com/luxiaoxun/NettyRpc

Fork: https://github.com/sw008/NettyRpc

此项目很适合学习多线程和Netty

RPC调用流程

大体思路:客户端通过ConcurrentHashMap<String, RPCFuture>保存请求ID和RPCFuture对象,然后把请求对象发送给服务端,返回RPCFuture对象给调用者。服务端处理成功后返回响应对象(包含请求ID)。客户端输入流接收响应对象,通过请求ID在ConcurrentHashMap中找到发送时创建的RPCFuture更新其相应信息,并更新其AQS的状态,release唤醒调用RPCFuture.get()而挂起的线程。

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()方法,接收客户端信息,并通过反射执行。    

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实现线程的挂起与唤醒。

sync对象实现了AbstractQueuedSynchronizer的tryRelease,tryAcquire方法。

当执行rpcFuture.done(response)时,将AQS中volatile int state通过CAS设置为1,唤醒已经执行rpcFuture.get()的所有线程。

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 status
        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;
        }
    }

猜你喜欢

转载自blog.csdn.net/shanchahua123456/article/details/85933937