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 实现的
posted @ 2022-02-22 23:32  s686编程传  阅读(286)  评论(1编辑  收藏  举报