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 转发到工作线程池。

服务端工作线程(out) 处理成功后通过Channel输出流返回响应对象(包含请求ID)给客户端。

4 客户端线程B(in) 通过输入流Handler,接收响应对象,通过返回的请求ID在ConcurrentHashMap<id, RPCFuture>中找到发送时创建的RPCFuture更新其相应信息,并更新其AQS的状态,release唤醒调用RPCFuture.get()而挂起的线程A。

(Callback:此步还可以拓展实现异步回调,与RPCFuture同理。即第一步发送时创建回调执行对象,保存到ConcurrentHashMap中,在此步找到此对象并执行,此时回调对象还可以获得request、response信息。也可设置一个单例回调对象,在返回输入流中执行其回调方法)

客户端线程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;
        }
    }

 

posted @ 2019-01-06 17:19  sw008  阅读(920)  评论(0编辑  收藏  举报