• RocketMQ(五) RemotingServer服务端 请求调用方法 源码分析


    RocketMQ(五) - RemotingServer服务端 请求调用方法 源码分析

    上一篇 《RocketMQ(三) - RemotingServer服务端启动 》 中 分析了 NettyRemotingServer 的启动源码,同时 也对其 继承接口 RemotingServer方法做了简单的注释说明,本篇主要详细 分析其中 三个请求方法 在NettyRemotingServer 中的实现细节。

    先回顾下 三个 请求调用方法 , 分别如下:

       
    
    	/**
         *  1. 同步调用
         * @param channel   通信通道
         * @param request   业务请求对象
         * @param timeoutMillis   超时时间
         * @return  响应结果封装
         */
        RemotingCommand invokeSync(final Channel channel, final RemotingCommand request,
            final long timeoutMillis) throws InterruptedException, RemotingSendRequestException,RemotingTimeoutException;
    
    
    
        /**
         *  2. 异步调用
         * @param channel  通信通道
         * @param request  业务请求对象
         * @param timeoutMillis  超时时间
         * @param invokeCallback  响应结果回调对象
         */
        void invokeAsync(final Channel channel, final RemotingCommand request, final long timeoutMillis,
            final InvokeCallback invokeCallback) throws InterruptedException,
            RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException;
    
    
    
        /**
         *  3. 单向调用 (不关注返回结果)
         * @param channel   通信通道
         * @param request   业务请求对象
         * @param timeoutMillis  超时时间
         */
        void invokeOneway(final Channel channel, final RemotingCommand request, final long timeoutMillis)
            throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException,RemotingSendRequestException;
    

    注意 本篇讲述的 这三个 调用 是建立在 服务端 主动向 客户端 发送请求的 情况下。

    NettyRemotingServer中的 三个请求调用方法 实际上都是 又一次调用了 其父类 NettyRemotingAbstract 的 对应方法, 因此 我们直接来看 NettyRemotingAbstract 类中 这三个方法的具体实现。

    1.同步调用

        /**
         *  服务器 主动向客户端 发起请求时 使用的方法。(当前方法 同步调用)
         *   什么是同步调用?
         *      服务器业务线程  需要在这里 等待client 返回结果之后 整个调用才完毕
         * @param channel        客户端ch
         * @param request        网络请求对象  remotingCommand
         * @param timeoutMillis  超时时长
         */
    	public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
            final long timeoutMillis)
            throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
    
            // 获取请求ID  Opaque
            final int opaque = request.getOpaque();
    
            try {
    			
                // 封装 响应结果 对象 (之后请求的返回结果会 存入该对象中)
                // 参数1: 客户端ch
                // 参数2: 请求ID
                // 参数3: 超时时间
                final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
    
                
                // 加入到 映射表 内
                // key: opaque  value: 响应结果对象
                this.responseTable.put(opaque, responseFuture);
    
                // 获取 客户端 地址信息
                final SocketAddress addr = channel.remoteAddress();
    
                // 将数据 写入 客户端 ch, 并设置个 回调监听器
                channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture f) throws Exception {
                       
                         //条件成立: 请求写成功
                        if (f.isSuccess()) {
                           // 设置 请求写状态 ok
                            responseFuture.setSendRequestOK(true);
                            return;
                        } 
                        // 条件成立: 请求写失败
                        else {
                            // 设置 请求写状态 false
                            responseFuture.setSendRequestOK(false);
                        }
    
                        // 写失败 才会走到这...
    
                        // 将当前请求的 responseFuture 从映射表 移除
                        responseTable.remove(opaque);
                        // 设置失败原因..
                        responseFuture.setCause(f.cause());
                        responseFuture.putResponse(null);
                        log.warn("send a request command to channel <" + addr + "> failed.");
                    }
                });
    
                // 同步调用的关键代码
                // 业务线程 在这里 进入挂起状态...    => countDownLatch.await()
                RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
    
                // 线程执行到这,有两种情况
                // 1. 正常情况, 客户端 返回数据了, IO线程 将业务线程 唤醒
                // 2. 超时
    
    
                if (null == responseCommand) {
                    // 执行进这里, 说明 超时 或 其它异常情况..
                    if (responseFuture.isSendRequestOK()) {
                        throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
                            responseFuture.getCause());
                    } else {
                        throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
                    }
                }
    
                // 正常..  这里返回 客户端的 请求结果。
                return responseCommand;
            } finally {
                // 从 响应结果映射表 移除该 响应结果对象
                this.responseTable.remove(opaque);
            }
        }
    
    

    2.异步调用

        /**
         *  服务器 主动 向客户端 发起请求时, 使用的方法 (异步)
         * @param channel     客户端ch
         * @param request      网络请求对象 remotingCommand
         * @param timeoutMillis  超时时长
         * @param invokeCallback  请求结果回调处理对象
         */
        public void invokeAsyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis,
            final InvokeCallback invokeCallback)
            throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
    
            // 获取 开始时间
            long beginStartTime = System.currentTimeMillis();
    
            // 请求ID
            final int opaque = request.getOpaque();
    
            // 获取信号量
            boolean acquired = this.semaphoreAsync.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
    
            if (acquired) {
                // 执行到这, 说明抢到了信号量, 当前线程可以发起请求, 服务器请求客户端的 并发 ,并未达到上限
    
                // once 对象 封装了 释放信号量的操作
                final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreAsync);
    
                // 计算 到这一步为止, 耗费的时间
                long costTime = System.currentTimeMillis() - beginStartTime;
    
                // 条件成立: 说明 已经超时了.. 就不用 再发起RPC了
                if (timeoutMillis < costTime) {
                    once.release();
                    throw new RemotingTimeoutException("invokeAsyncImpl call timeout");
                }
    
                //  参数1: 客户端ch
                //  参数2: 请求ID
                //  参数3:  剩余的超时时间
                //  参数4: 回调处理对象
                //  参数5: 信号量释放操作封装对象
                final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis - costTime, invokeCallback, once);
    
                // 将 responseFuture 加入 响应 映射表,key 其实是 RequestId
                this.responseTable.put(opaque, responseFuture);
                try {
    
                    // 1. 业务线程 将数据 交给netty, netty IO线程 接管 写 和 刷 数据的操作。
                    // 2. 注册 写 刷 操作的 监听器。 监听器由IO线程回调
                    channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture f) throws Exception {
                            if (f.isSuccess()) {
                                // 写刷 成功 ,设置 responseFutrue 发生状态为 true
                                responseFuture.setSendRequestOK(true);
                                return;
                            }
                            // 执行到这, 发送失败
                            requestFail(opaque);
                            log.warn("send a request command to channel <{}> failed.", RemotingHelper.parseChannelRemoteAddr(channel));
                        }
                    });
                } catch (Exception e) {
                    responseFuture.release();
                    log.warn("send a request command to channel <" + RemotingHelper.parseChannelRemoteAddr(channel) + "> Exception", e);
                    throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
                }
            } else {
                // 执行到这, 说明获取 信号量失败, 说明当前并发高
                if (timeoutMillis <= 0) {
                    throw new RemotingTooMuchRequestException("invokeAsyncImpl invoke too fast");
                } else {
                    String info =
                        String.format("invokeAsyncImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d",
                            timeoutMillis,
                            this.semaphoreAsync.getQueueLength(),
                            this.semaphoreAsync.availablePermits()
                        );
                    log.warn(info);
                    throw new RemotingTimeoutException(info);
                }
            }
        }
    

    3.单向调用

    /**
         *   服务器 主动向 客户端 发起请求时,(此方法 不关注结果  单向请求)
         * @param channel    客户端ch
         * @param request    网络请求对象 remotingCommand
         * @param timeoutMillis  超时时长
         */
        public void invokeOnewayImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis)
            throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
    
            // 设置 标记, 对端 检查标记  就可以知道 这个请求是一个单向请求
            request.markOnewayRPC();
    
            // 申请信号量
            boolean acquired = this.semaphoreOneway.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
            if (acquired) {
    
                // 释放信号量逻辑的封装对象
                final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreOneway);
                try {
    
                    // 1. 将数据交给ch, 这里数据处理发送的逻辑 由netty 线程完成
                    // 2. 添加 写刷数据操作的 监听器。 由Netty IO线程回调
                    channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture f) throws Exception {
                            // 释放信号量
                            once.release();
                            if (!f.isSuccess()) {
                                log.warn("send a request command to channel <" + channel.remoteAddress() + "> failed.");
                            }
                        }
                    });
                } catch (Exception e) {
                    once.release();
                    log.warn("write send a request command to channel <" + channel.remoteAddress() + "> failed.");
                    throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
                }
            } 
            
            // 条件成立: 说明并发高,达到了限制
            else {
                if (timeoutMillis <= 0) {
                    throw new RemotingTooMuchRequestException("invokeOnewayImpl invoke too fast");
                } else {
                    String info = String.format(
                        "invokeOnewayImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d",
                        timeoutMillis,
                        this.semaphoreOneway.getQueueLength(),
                        this.semaphoreOneway.availablePermits()
                    );
                    log.warn(info);
                    throw new RemotingTimeoutException(info);
                }
            }
        }
    

    4. 总结

    NettyRemotingServer 三个 请求调用的方法 很简单, 没什么好说的。 这里总结下:

    方法 是否关注响应结果 是否又并发限制
    invokeSyncImpl 关注 无并发限制
    invokeAsyncImpl 关注 有并发限制
    invokeOnewayImpl 不关注 有并发限制

    其中注意的是:

    1. 关注结果, 实际上就是将 对象 封装成 ResponseFuture 对象
    2. 同步调用, 就是利用 ResponseFuture 对象中的 countDownLatch 实现的
    3. 并发限制,就是通过控制Semaphore 实现的
  • 相关阅读:
    Singleton(单例模式)的一种实现 -- 基于【惰性】适用于【大对象】的一种生产实践
    001.Getting Started -- 【入门指南】
    SparkStreaming高级算子应用【combineByKey、transform,checkpoint】
    Solr基础理论【相关度计算】
    Solr基础理论【排名检索、查准率、查全率】
    Solr基础理论【倒排索引,模糊查询】
    Impala快速入门
    Redis特点分析及性能优化
    电力系统【第八章:电力系统不对称故障的分析与计算】
    SparkStreaming之checkpoint检查点
  • 原文地址:https://www.cnblogs.com/s686zhou/p/15925519.html
Copyright © 2020-2023  润新知