RocketMQ源码分析之通信模块


前言

本篇文章将从以下几方面分析RocketMQ的通信模块:

  • RocketMQ的线程模型
  • RocketMQ如何实现同步、异步及单向调用

一、RocketMQ线程模型

在RocketMQ中NettyRemotingServer与NettyRemotingClient分别代表客户端与服务端,下面分别看下NettyRemotingServer与NettyRemotingClient的初始化及启动过程。

1. NettyRemotingServer初始化

NettyRemotingServer初始化的过程具体如下:
(1)初始化ServerBootstrap,用于引导服务端
(2)设置nettyServerConfig和channelEventListener,其中nettyServerConfig是服务端的通信配置
(3)初始化publicExecutor线程池
(4)判断服务端是否启用了Epoll,判断条件有三个:当前平台是否是Linux、broker端配置文件的配置项useEpollNativeSelector是否为true、当前平台是否支持Epoll
(5)如果启用了Epoll则eventLoopGroupBoss和eventLoopGroupSelector在初始化时将使用EpollEventLoopGroup,否则使用NioEventLoopGroup(eventLoopGroupBoss负责监听TCP网络连接请求事件OP_ACCEPT,建立好连接后提交注册任务到eventLoopGroupSelector,eventLoopGroupSelector会将socketChannel注册到selector,监听OP_READ事件,当网络数据可读后,提交任务到defaultEventExecutorGroup)

public NettyRemotingServer(final NettyServerConfig nettyServerConfig,
        final ChannelEventListener channelEventListener) {
        super(nettyServerConfig.getServerOnewaySemaphoreValue(), nettyServerConfig.getServerAsyncSemaphoreValue());
        this.serverBootstrap = new ServerBootstrap();
        this.nettyServerConfig = nettyServerConfig;
        this.channelEventListener = channelEventListener;

        int publicThreadNums = nettyServerConfig.getServerCallbackExecutorThreads();
        if (publicThreadNums <= 0) {
            publicThreadNums = 4;
        }

        this.publicExecutor = Executors.newFixedThreadPool(publicThreadNums, new ThreadFactory() {
            private AtomicInteger threadIndex = new AtomicInteger(0);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "NettyServerPublicExecutor_" + this.threadIndex.incrementAndGet());
            }
        });

        if (useEpoll()) {
            this.eventLoopGroupBoss = new EpollEventLoopGroup(1, new ThreadFactory() {
                private AtomicInteger threadIndex = new AtomicInteger(0);

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, String.format("NettyEPOLLBoss_%d", this.threadIndex.incrementAndGet()));
                }
            });

            this.eventLoopGroupSelector = new EpollEventLoopGroup(nettyServerConfig.getServerSelectorThreads(), new ThreadFactory() {
                private AtomicInteger threadIndex = new AtomicInteger(0);
                private int threadTotal = nettyServerConfig.getServerSelectorThreads();

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, String.format("NettyServerEPOLLSelector_%d_%d", threadTotal, this.threadIndex.incrementAndGet()));
                }
            });
        } else {
            this.eventLoopGroupBoss = new NioEventLoopGroup(1, new ThreadFactory() {
                private AtomicInteger threadIndex = new AtomicInteger(0);

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, String.format("NettyNIOBoss_%d", this.threadIndex.incrementAndGet()));
                }
            });

            this.eventLoopGroupSelector = new NioEventLoopGroup(nettyServerConfig.getServerSelectorThreads(), new ThreadFactory() {
                private AtomicInteger threadIndex = new AtomicInteger(0);
                private int threadTotal = nettyServerConfig.getServerSelectorThreads();

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, String.format("NettyServerNIOSelector_%d_%d", threadTotal, this.threadIndex.incrementAndGet()));
                }
            });
        }

        loadSslContext();
    }

2. NettyRemotingServer启动

NettyRemotingServer启动过程具体如下:
(1)初始化defaultEventExecutorGroup
(2)调用prepareSharableHandlers方法初始化handshakeHandler、encoder、connectionManageHandler和serverHandler,其中serverHandler是业务处理的handler
(3)通过serverBootstrap来引导服务端,这里需要注意handler的顺序以及其类型(Inbound或者 Outbound)
(4)serverBootstrap绑定端口并同步返回绑定结果
(5)如果channelEventListener不为null,则启动nettyEventExecutor线程池(channelEventListener是监测连接的)
(6)启动定时任务,定期扫描已经过期的请求

public void start() {
        this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(
            nettyServerConfig.getServerWorkerThreads(),
            new ThreadFactory() {

                private AtomicInteger threadIndex = new AtomicInteger(0);

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, "NettyServerCodecThread_" + this.threadIndex.incrementAndGet());
                }
            });

        prepareSharableHandlers();

        ServerBootstrap childHandler =
            this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector)
                .channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .option(ChannelOption.SO_REUSEADDR, true)
                .option(ChannelOption.SO_KEEPALIVE, false)
                .childOption(ChannelOption.TCP_NODELAY, true)
                .childOption(ChannelOption.SO_SNDBUF, nettyServerConfig.getServerSocketSndBufSize())
                .childOption(ChannelOption.SO_RCVBUF, nettyServerConfig.getServerSocketRcvBufSize())
                .localAddress(new InetSocketAddress(this.nettyServerConfig.getListenPort()))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline()
                            .addLast(defaultEventExecutorGroup, HANDSHAKE_HANDLER_NAME, handshakeHandler)
                            .addLast(defaultEventExecutorGroup,
                                encoder,
                                new NettyDecoder(),
                                new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()),
                                connectionManageHandler,
                                serverHandler
                            );
                    }
                });

        if (nettyServerConfig.isServerPooledByteBufAllocatorEnable()) {
            childHandler.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
        }

        try {
            ChannelFuture sync = this.serverBootstrap.bind().sync();
            InetSocketAddress addr = (InetSocketAddress) sync.channel().localAddress();
            this.port = addr.getPort();
        } catch (InterruptedException e1) {
            throw new RuntimeException("this.serverBootstrap.bind().sync() InterruptedException", e1);
        }

        if (this.channelEventListener != null) {
            this.nettyEventExecutor.start();
        }

        this.timer.scheduleAtFixedRate(new TimerTask() {

            @Override
            public void run() {
                try {
                    NettyRemotingServer.this.scanResponseTable();
                } catch (Throwable e) {
                    log.error("scanResponseTable exception", e);
                }
            }
        }, 1000 * 3, 1000);
    }

3. NettyRemotingServer线程模型

下图是服务端的线程模型,从图中可以看到RocketMQ中NettyRemotingServer遵循了reactor多线程模型并在其基础上做了优化和扩展。其中EventLoopGroupBoss为reactor主线程,负责监听TCP网络连接请求事件OP_ACCEPT,建立好连接后提交注册任务到eventLoopGroupSelector。eventLoopGroupSelector会将socketChannel注册到selector上,监听OP_READ事件,当网络数据可读后提交任务到defaultEventExecutorGroup。defaultEventExecutorGroup在收到数据后会先对数据进行SSL验证、编解码、空闲检查、网络连接管理,最后会交给NettyServerHandler进行业务上处理。在进行业务处理时会先在服务端缓存中processorTable根据请求的requestCode获取该类请求对应的processor和线程池,然后将请求封装成一个RequestTask,最后提交到线程池中执行。
在这里插入图片描述
补充:
这里有一个很重要的数据缓存–processorTable,其数据结构是<requestCode, Pair<processor, executor>>,那么该缓存中的数据是在哪里添加的呢?这其实与broker的启动有关,在broker启动过程中会对BrokerController进行初始化,在其初始化的过程中与processorTable相关的两点是:
(1)初始化各类业务处理的线程池
(2)调用registerProcessor方法将请求的code、处理该请求的processor以及线程池注册到broker端的remotingServer和fastRemotingServer中
这样就完成了processorTable中数据的添加,此外还需要注意一点就是remotingServer和fastRemotingServer都是NettyRemotingServer的实例,其区别在于两个实例的端口是不一样的,remotingServer的端口是10911,fastRemotingServer的端口是10909,那么问题来了:fastRemotingServer和remotingServer除端口外其余网络配置都一样,那么为什么还要再多一个实例呢,官方的回答是隔离读写操作,具体见:https://github.com/apache/rocketmq/issues/1510
最后就是pipeline中的handler是可以进行动态调整的。

if (result) {
            this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.clientHousekeepingService);
            NettyServerConfig fastConfig = (NettyServerConfig) this.nettyServerConfig.clone();
            fastConfig.setListenPort(nettyServerConfig.getListenPort() - 2);
            this.fastRemotingServer = new NettyRemotingServer(fastConfig, this.clientHousekeepingService);
            this.sendMessageExecutor = new BrokerFixedThreadPoolExecutor(
                this.brokerConfig.getSendMessageThreadPoolNums(),
                this.brokerConfig.getSendMessageThreadPoolNums(),
                1000 * 60,
                TimeUnit.MILLISECONDS,
                this.sendThreadPoolQueue,
                new ThreadFactoryImpl("SendMessageThread_"));

            this.pullMessageExecutor = new BrokerFixedThreadPoolExecutor(
                this.brokerConfig.getPullMessageThreadPoolNums(),
                this.brokerConfig.getPullMessageThreadPoolNums(),
                1000 * 60,
                TimeUnit.MILLISECONDS,
                this.pullThreadPoolQueue,
                new ThreadFactoryImpl("PullMessageThread_"));

            this.replyMessageExecutor = new BrokerFixedThreadPoolExecutor(
                this.brokerConfig.getProcessReplyMessageThreadPoolNums(),
                this.brokerConfig.getProcessReplyMessageThreadPoolNums(),
                1000 * 60,
                TimeUnit.MILLISECONDS,
                this.replyThreadPoolQueue,
                new ThreadFactoryImpl("ProcessReplyMessageThread_"));

            this.queryMessageExecutor = new BrokerFixedThreadPoolExecutor(
                this.brokerConfig.getQueryMessageThreadPoolNums(),
                this.brokerConfig.getQueryMessageThreadPoolNums(),
                1000 * 60,
                TimeUnit.MILLISECONDS,
                this.queryThreadPoolQueue,
                new ThreadFactoryImpl("QueryMessageThread_"));

            this.adminBrokerExecutor =
                Executors.newFixedThreadPool(this.brokerConfig.getAdminBrokerThreadPoolNums(), new ThreadFactoryImpl(
                    "AdminBrokerThread_"));

            this.clientManageExecutor = new ThreadPoolExecutor(
                this.brokerConfig.getClientManageThreadPoolNums(),
                this.brokerConfig.getClientManageThreadPoolNums(),
                1000 * 60,
                TimeUnit.MILLISECONDS,
                this.clientManagerThreadPoolQueue,
                new ThreadFactoryImpl("ClientManageThread_"));

            this.heartbeatExecutor = new BrokerFixedThreadPoolExecutor(
                this.brokerConfig.getHeartbeatThreadPoolNums(),
                this.brokerConfig.getHeartbeatThreadPoolNums(),
                1000 * 60,
                TimeUnit.MILLISECONDS,
                this.heartbeatThreadPoolQueue,
                new ThreadFactoryImpl("HeartbeatThread_", true));

            this.endTransactionExecutor = new BrokerFixedThreadPoolExecutor(
                this.brokerConfig.getEndTransactionThreadPoolNums(),
                this.brokerConfig.getEndTransactionThreadPoolNums(),
                1000 * 60,
                TimeUnit.MILLISECONDS,
                this.endTransactionThreadPoolQueue,
                new ThreadFactoryImpl("EndTransactionThread_"));

            this.consumerManageExecutor =
                Executors.newFixedThreadPool(this.brokerConfig.getConsumerManageThreadPoolNums(), new ThreadFactoryImpl(
                    "ConsumerManageThread_"));

            this.registerProcessor();

4. NettyRemotingClient初始化

NettyRemotingClient初始化的过程具体如下:
(1)初始化两个信号量,其中semaphoreOneway线程并发数是256、semaphoreAsync线程并发数是64,semaphoreOneway是用来控制单向调用并发量,semaphoreAsync是用来控制异步调用并发量
(2)设置nettyClientConfig和channelEventListener,其中nettyClientConfig是客户端端的通信配置
(3)初始化publicExecutor线程池
(4)初始化eventLoopGroupWorker
(5)判断客户端是否启用了TLS,如果启用了则构建sslContext

public NettyRemotingClient(final NettyClientConfig nettyClientConfig,
        final ChannelEventListener channelEventListener) {
        super(nettyClientConfig.getClientOnewaySemaphoreValue(), nettyClientConfig.getClientAsyncSemaphoreValue());
        this.nettyClientConfig = nettyClientConfig;
        this.channelEventListener = channelEventListener;

        int publicThreadNums = nettyClientConfig.getClientCallbackExecutorThreads();
        if (publicThreadNums <= 0) {
            publicThreadNums = 4;
        }

        this.publicExecutor = Executors.newFixedThreadPool(publicThreadNums, new ThreadFactory() {
            private AtomicInteger threadIndex = new AtomicInteger(0);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "NettyClientPublicExecutor_" + this.threadIndex.incrementAndGet());
            }
        });

        this.eventLoopGroupWorker = new NioEventLoopGroup(1, new ThreadFactory() {
            private AtomicInteger threadIndex = new AtomicInteger(0);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, String.format("NettyClientSelector_%d", this.threadIndex.incrementAndGet()));
            }
        });

        if (nettyClientConfig.isUseTLS()) {
            try {
                sslContext = TlsHelper.buildSslContext(true);
                log.info("SSL enabled for client");
            } catch (IOException e) {
                log.error("Failed to create SSLContext", e);
            } catch (CertificateException e) {
                log.error("Failed to create SSLContext", e);
                throw new RuntimeException("Failed to create SSLContext", e);
            }
        }
    }

5. NettyRemotingClient启动

NettyRemotingClient的启动过程与NettyRemotingServer类似,具体如下:
(1)初始化defaultEventExecutorGroup
(2)通过bootstrap来引导客户端,这里同样需要注意handler的顺序及其类型(Inbound或者 Outbound),NettyClientHandler是客户端业务处理的handler
(3)启动定时任务,定期扫描已经过期的请求
(4)如果channelEventListener不为空则启动nettyEventExecutor线程池

public void start() {
        this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(
            nettyClientConfig.getClientWorkerThreads(),
            new ThreadFactory() {

                private AtomicInteger threadIndex = new AtomicInteger(0);

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, "NettyClientWorkerThread_" + this.threadIndex.incrementAndGet());
                }
            });

        Bootstrap handler = this.bootstrap.group(this.eventLoopGroupWorker).channel(NioSocketChannel.class)
            .option(ChannelOption.TCP_NODELAY, true)
            .option(ChannelOption.SO_KEEPALIVE, false)
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyClientConfig.getConnectTimeoutMillis())
            .option(ChannelOption.SO_SNDBUF, nettyClientConfig.getClientSocketSndBufSize())
            .option(ChannelOption.SO_RCVBUF, nettyClientConfig.getClientSocketRcvBufSize())
            .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    if (nettyClientConfig.isUseTLS()) {
                        if (null != sslContext) {
                            pipeline.addFirst(defaultEventExecutorGroup, "sslHandler", sslContext.newHandler(ch.alloc()));
                            log.info("Prepend SSL handler");
                        } else {
                            log.warn("Connections are insecure as SSLContext is null!");
                        }
                    }
                    pipeline.addLast(
                        defaultEventExecutorGroup,
                        new NettyEncoder(),
                        new NettyDecoder(),
                        new IdleStateHandler(0, 0, nettyClientConfig.getClientChannelMaxIdleTimeSeconds()),
                        new NettyConnectManageHandler(),
                        new NettyClientHandler());
                }
            });

        this.timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                try {
                    NettyRemotingClient.this.scanResponseTable();
                } catch (Throwable e) {
                    log.error("scanResponseTable exception", e);
                }
            }
        }, 1000 * 3, 1000);

        if (this.channelEventListener != null) {
            this.nettyEventExecutor.start();
        }
    }

6. NettyRemotingClient线程模型

NettyRemotingClient的线程模型与NettyRemotingServer类似,这里就不过多阐述了。
在这里插入图片描述

二、RocketMQ如何实现同步、异步及单向调用

1.同步调用

同步调用的实现如下:
(1)分别启动客户端和服务端
(2)客户端调用invokeSync方法发起同步调用,在该方法中会调用getAndCreateChannel方法来建立与服务端的连接,创建连接的核心是在createChannel方法,具体如下:

private Channel createChannel(final String addr) throws InterruptedException {
        ChannelWrapper cw = this.channelTables.get(addr);
        if (cw != null && cw.isOK()) {
            return cw.getChannel();
        }

        if (this.lockChannelTables.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
            try {
                boolean createNewConnection;
                cw = this.channelTables.get(addr);
                if (cw != null) {

                    if (cw.isOK()) {
                        return cw.getChannel();
                    } else if (!cw.getChannelFuture().isDone()) {
                        createNewConnection = false;
                    } else {
                        this.channelTables.remove(addr);
                        createNewConnection = true;
                    }
                } else {
                    createNewConnection = true;
                }

                if (createNewConnection) {
                    ChannelFuture channelFuture = this.bootstrap.connect(RemotingHelper.string2SocketAddress(addr));
                    log.info("createChannel: begin to connect remote host[{}] asynchronously", addr);
                    cw = new ChannelWrapper(channelFuture);
                    this.channelTables.put(addr, cw);
                }
            } catch (Exception e) {
                log.error("createChannel: create channel exception", e);
            } finally {
                this.lockChannelTables.unlock();
            }
        } else {
            log.warn("createChannel: try to lock channel table, but timeout, {}ms", LOCK_TIMEOUT_MILLIS);
        }

        if (cw != null) {
            ChannelFuture channelFuture = cw.getChannelFuture();
            if (channelFuture.awaitUninterruptibly(this.nettyClientConfig.getConnectTimeoutMillis())) {
                if (cw.isOK()) {
                    log.info("createChannel: connect remote host[{}] success, {}", addr, channelFuture.toString());
                    return cw.getChannel();
                } else {
                    log.warn("createChannel: connect remote host[" + addr + "] failed, " + channelFuture.toString(), channelFuture.cause());
                }
            } else {
                log.warn("createChannel: connect remote host[{}] timeout {}ms, {}", addr, this.nettyClientConfig.getConnectTimeoutMillis(),
                    channelFuture.toString());
            }
        }

        return null;
    }

(3)在invokeSync方法中同步调用的核心实现在invokeSyncImpl方法中,在该方法中重要的操作如下:

  • 将本次请求的opaque和responseFuture存储在responseTable中,其中opaque相当于requestId,在同一个连接上的不同请求标识码,与响应消息中的相对应,responseFuture中记录了opaque、请求的连接channel、响应responseCommand、超时时间、回调函数和信号量(在同步调用中回调函数即信号量都为空,在异步调用中回调函数和信号量都不为空,在单向调用中回调函数为空但是信号量不为空)
  • 通过channel的writeAndFlush方法将request发送给服务端
  • 通过对responseFuture执行waitResponse方法来阻塞当前线程知道收到响应(这里是使用CountDownLatch来实现的)
public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
        final long timeoutMillis)
        throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
        final int opaque = request.getOpaque();

        try {
            final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
            this.responseTable.put(opaque, responseFuture);
            final SocketAddress addr = channel.remoteAddress();
            channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture f) throws Exception {
                    if (f.isSuccess()) {
                        responseFuture.setSendRequestOK(true);
                        return;
                    } else {
                        responseFuture.setSendRequestOK(false);
                    }

                    responseTable.remove(opaque);
                    responseFuture.setCause(f.cause());
                    responseFuture.putResponse(null);
                    log.warn("send a request command to channel <" + addr + "> failed.");
                }
            });

            RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
            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);
        }
    }


public RemotingCommand waitResponse(final long timeoutMillis) throws InterruptedException {
        this.countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
        return this.responseCommand;
    }

这里还有一个小的细节需要注意:在invokeSyncImpl方法中将请求发送给服务端时添加了一个监听器,这里如果发送成功则会将responseFuture的sendRequestOK属性设置为true否则设置为false,在客户端线程被唤醒后会对响应为空的情况进行进一步分析,如果responseFuture的sendRequestOK属性为true则表示发送成功请求成功但是没有收到响应此时会抛出超时异常;如果responseFuture的sendRequestOK属性为false则表示发送请求失败此时会抛出发送失败异常,所以可以看出发送失败和超时是两种异常。
(4)服务端的eventLoopGroupSelector在监听到该请求可读后会将其提交给defaultEventExcutorGroup,之后defaultEventExcutorGroup会按照服务端启动过程中添加的handler来对请求进行处理,例如SSL验证、解码数据、空闲检查、网络连接管理以及最终的业务处理(NettyServerHandler)。这里从NettyServerHandler来看下broker端是如何处理请求:NettyServerHandler重写了channelRead0方法,该方法调用了processMessageReceived方法,在这个方法中按照RemotingCommand的类型进行了区分,分别是REQUEST_COMMAND和RESPONSE_COMMAND,这里收到的是REQUEST_COMMAND所以详细看下processRequestCommand方法,在该方法中会先从processorTable中根据请求的requestCode获取该类型请求对应的processor和线程池,然后构建一个RequestTask,最后提交给该类型请求对应的线程池中进行处理,这里注意下RequestTask中会将请求提交给对应processor的来处理,此外还需要注意一点:在processRequestCommand方法中在封装任务时有一个callback,在这个callback中对请求的类型进行了判断,如果不是单向调用,即同步调用或者异步调用,则在响应中设置好收到的请求的opaque并将响应返回给客户端。

public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
        final RemotingCommand cmd = msg;
        if (cmd != null) {
            switch (cmd.getType()) {
                case REQUEST_COMMAND:
                    processRequestCommand(ctx, cmd);
                    break;
                case RESPONSE_COMMAND:
                    processResponseCommand(ctx, cmd);
                    break;
                default:
                    break;
            }
        }
    }

public void processRequestCommand(final ChannelHandlerContext ctx, final RemotingCommand cmd) {
        final Pair<NettyRequestProcessor, ExecutorService> matched = this.processorTable.get(cmd.getCode());
        final Pair<NettyRequestProcessor, ExecutorService> pair = null == matched ? this.defaultRequestProcessor : matched;
        final int opaque = cmd.getOpaque();

        if (pair != null) {
            Runnable run = new Runnable() {
                @Override
                public void run() {
                    try {
                        doBeforeRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd);
                        final RemotingResponseCallback callback = new RemotingResponseCallback() {
                            @Override
                            public void callback(RemotingCommand response) {
                                doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd, response);
                                if (!cmd.isOnewayRPC()) {
                                    if (response != null) {
                                        response.setOpaque(opaque);
                                        response.markResponseType();
                                        try {
                                            ctx.writeAndFlush(response);
                                        } catch (Throwable e) {
                                            log.error("process request over, but response failed", e);
                                            log.error(cmd.toString());
                                            log.error(response.toString());
                                        }
                                    } else {
                                    }
                                }
                            }
                        };
                        if (pair.getObject1() instanceof AsyncNettyRequestProcessor) {
                            AsyncNettyRequestProcessor processor = (AsyncNettyRequestProcessor)pair.getObject1();
                            processor.asyncProcessRequest(ctx, cmd, callback);
                        } else {
                            NettyRequestProcessor processor = pair.getObject1();
                            RemotingCommand response = processor.processRequest(ctx, cmd);
                            callback.callback(response);
                        }
                    } catch (Throwable e) {
                        log.error("process request exception", e);
                        log.error(cmd.toString());

                        if (!cmd.isOnewayRPC()) {
                            final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_ERROR,
                                RemotingHelper.exceptionSimpleDesc(e));
                            response.setOpaque(opaque);
                            ctx.writeAndFlush(response);
                        }
                    }
                }
            };

            if (pair.getObject1().rejectRequest()) {
                final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_BUSY,
                    "[REJECTREQUEST]system busy, start flow control for a while");
                response.setOpaque(opaque);
                ctx.writeAndFlush(response);
                return;
            }

            try {
                final RequestTask requestTask = new RequestTask(run, ctx.channel(), cmd);
                pair.getObject2().submit(requestTask);
            } catch (RejectedExecutionException e) {
                if ((System.currentTimeMillis() % 10000) == 0) {
                    log.warn(RemotingHelper.parseChannelRemoteAddr(ctx.channel())
                        + ", too many requests and system thread pool busy, RejectedExecutionException "
                        + pair.getObject2().toString()
                        + " request code: " + cmd.getCode());
                }

                if (!cmd.isOnewayRPC()) {
                    final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_BUSY,
                        "[OVERLOAD]system busy, start flow control for a while");
                    response.setOpaque(opaque);
                    ctx.writeAndFlush(response);
                }
            }
        } else {
            String error = " request type " + cmd.getCode() + " not supported";
            final RemotingCommand response =
                RemotingCommand.createResponseCommand(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED, error);
            response.setOpaque(opaque);
            ctx.writeAndFlush(response);
            log.error(RemotingHelper.parseChannelRemoteAddr(ctx.channel()) + error);
        }
    }

(5)服务端处理完请求后会调用ChannelHandlerContext.writeAndFlush从当前handler依次调用所有的出站handler最终将响应返回给客户端
(6)客户端在收到服务端的响应后最终是在NettyClientHandler中处理的,NettyClientHandler重写了channelRead0方法,同样在processMessageReceived方法中对RemotingCommand的类型进行了区分,这里由于是RESPONSE_COMMAND,所以会执行processResponseCommand方法,该方法首先会根据响应中的opaque从responseTable中获取该请求对应的响应结果responseFuture,然后将响应结果设置到responseFuture,然后从responseTable中删除该记录。最后会判断responseFuture中是否有回调函数,由于是同步调用所以没有回调函数,此时会对responseFuture中的countDownLatch执行countDown操作进而唤醒被阻塞的线程并返回responseFuture中的responseCommand

public void processResponseCommand(ChannelHandlerContext ctx, RemotingCommand cmd) {
        final int opaque = cmd.getOpaque();
        final ResponseFuture responseFuture = responseTable.get(opaque);
        if (responseFuture != null) {
            responseFuture.setResponseCommand(cmd);

            responseTable.remove(opaque);

            if (responseFuture.getInvokeCallback() != null) {
                executeInvokeCallback(responseFuture);
            } else {
                responseFuture.putResponse(cmd);
                responseFuture.release();
            }
        } else {
            log.warn("receive response, but not matched any request, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
            log.warn(cmd.toString());
        }
    }

最后用一张图来总结同步调用的实现过程,具体如下:
在这里插入图片描述

2.异步调用

异步调用的实现如下:
(1)分别启动客户端和服务端
(2)客户端调用invokeAsync方法发起异步调用,在该方法中会调用getAndCreateChannel方法来建立与服务端的连接,创建连接的核心是在createChannel方法与同步调用是一样的
(3)在invokeAsync方法中同步调用的核心实现在invokeAsyncImpl方法中,在该方法中重要的操作如下:

  • 从semaphoreAsync中获取信号量,该信号量用来控制异步调用并发量
  • 与同步调用一样,构建responseFuture对象并将opaque与responseFuture缓存在responseTable中,与同步调用不一样的是responseFuture对象中会包含回调函数及获取的信号量
  • 通过channel的writeAndFlush方法将请求发送给服务端
public void invokeAsyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis,
        final InvokeCallback invokeCallback)
        throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
        long beginStartTime = System.currentTimeMillis();
        final int opaque = request.getOpaque();
        boolean acquired = this.semaphoreAsync.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
        if (acquired) {
            final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreAsync);
            long costTime = System.currentTimeMillis() - beginStartTime;
            if (timeoutMillis < costTime) {
                once.release();
                throw new RemotingTimeoutException("invokeAsyncImpl call timeout");
            }

            final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis - costTime, invokeCallback, once);
            this.responseTable.put(opaque, responseFuture);
            try {
                channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture f) throws Exception {
                        if (f.isSuccess()) {
                            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);
            }
        }
    }

(4)服务端收到请求后的处理流程与同步调用是一样的,这里不再过多阐述,最终服务端将请求发送给客户端,这里需要值得注意的是:在processRequestCommand方法中在封装任务时有一个callback,在这个callback中对请求的类型进行了判断,如果不是单向调用,即同步调用或者异步调用,则在响应中设置好收到的请求的opaque并将响应返回给客户端
(5)客户端在收到服务端的响应后最终是在NettyClientHandler中处理的,NettyClientHandler重写了channelRead0方法,同样在processMessageReceived方法中对RemotingCommand的类型进行了区分,这里由于是RESPONSE_COMMAND,所以会执行processResponseCommand方法,该方法首先会根据响应中的opaque从responseTable中获取该请求对应的响应结果responseFuture,然后将响应结果设置到responseFuture,然后从responseTable中删除该记录。最后会判断responseFuture中是否有回调函数,由于是异步调用所以是有回调函数的,最后会执行executeInvokeCallback方法,该方法会将回调函数封装成一个任务并将其提交到publicExecutor线程池中执行,执行完回调函数后会释放responseFuture,具体就是将持有的信号量释放

private void executeInvokeCallback(final ResponseFuture responseFuture) {
        boolean runInThisThread = false;
        ExecutorService executor = this.getCallbackExecutor();
        if (executor != null) {
            try {
                executor.submit(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            responseFuture.executeInvokeCallback();
                        } catch (Throwable e) {
                            log.warn("execute callback in executor exception, and callback throw", e);
                        } finally {
                            responseFuture.release();
                        }
                    }
                });
            } catch (Exception e) {
                runInThisThread = true;
                log.warn("execute callback in executor exception, maybe executor busy", e);
            }
        } else {
            runInThisThread = true;
        }

        if (runInThisThread) {
            try {
                responseFuture.executeInvokeCallback();
            } catch (Throwable e) {
                log.warn("executeInvokeCallback Exception", e);
            } finally {
                responseFuture.release();
            }
        }
    }

最后用一张图来总结异步调用的实现过程,具体如下:
在这里插入图片描述


3.单向调用

单向调用的实现如下:
(1)分别启动客户端和服务端
(2)客户端调用invokeOneway方法发起异步调用,在该方法中会调用getAndCreateChannel方法来建立与服务端的连接,创建连接的核心是在createChannel方法与同步调用是一样的
(3)在invokeOneway方法中同步调用的核心实现在invokeOnewayImpl方法中,在该方法中重要的操作如下:

  • 从semaphoreOneway中获取信号量,该信号量用来控制单向调用并发量
  • 与同步调用一样,构建responseFuture对象并将opaque与responseFuture缓存在responseTable中,与同步调用不一样的是responseFuture对象中会包含获取的信号量
  • 通过channel的writeAndFlush方法将请求发送给服务端
  • 在请求发送成功后在ChannelFutureListener中会释放信号量

(4)服务端收到请求后的处理流程与同步调用是一样的,这里不再过多阐述,但是有一点需要注意:在processRequestCommand方法中在封装任务时有一个callback,在这个callback中对请求的类型进行了判断,如果是单向调用则不会将响应返回给客户端

最后用一张图来总结单向调用的实现过程,具体如下:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值