为了账号安全,请及时绑定邮箱和手机立即绑定

RocketMQ 底层通信机制 源码分析

标签:
Java

概述

RocketMQ 底层通讯是使用Netty来实现的。
下面我们通过源码分析下RocketMQ是怎么利用Netty进行通讯的。

本文分析的是RocketMQ 最新版本 4.3.2版本。

RocketMQ 项目结构

首先来看下 RocketMQ 模块构成。


webp


通过 RocketMQ 项目结构可以看出,RocketMQ 分了好多模块。 broker、client、filter、namesrv、remoting 等。

大家比较熟悉的几个模块对应的源码如下:
Broker Master 和 Slave 对应的 broker 模块。
Producer 和 Consumer 对应的是 client 模块。
NameSerer 服务对应的是 namesrv 模块。

而各个服务之间的通讯则使用的 remoting 模块。

Remoting 模块

webp

通过romoting 的模块结构大概了解,RocketMQ 通讯使用了Netty进行传输通讯。并在 org.apache.rocketmq.remoting.protocol 包中自定义了通讯协议。

通信模块主要接口和类

RemotingService 接口

public interface RemotingService {    //开启服务
    void start();    //关闭服务
    void shutdown();    //注册 hook (可以在调用之前和调用之后做一些扩展处理)
    void registerRPCHook(RPCHook rpcHook);
}

RemotingService 定义了服务端和客户端都需要的三个接口。
registerRPCHook() 方法可以注册一个 hook。可以在远程通信之前和通信之后,执行用户自定的一些处理。类似前置处理器和后置处理器。

RPCHook 接口

public interface RPCHook {    void doBeforeRequest(final String remoteAddr, final RemotingCommand request);    void doAfterResponse(final String remoteAddr, final RemotingCommand request,        final RemotingCommand response);
}

在启动服务之前,可以把自己实现的 RPCHook 注册到服务中,执行远程调用的时候处理一些业务逻辑。比如打印请求和响应的日志信息。

RemotingServer  和 RemotingClient 接口

RemotingServer  和 RemotingClient 接口都继承了RemotingService 接口,并扩展了自己特有的方法。

RemotingServer 接口

public interface RemotingServer extends RemotingService {    //注册一个处理请求的处理器, 根据requestCode, 获取处理器,处理请求
    void registerProcessor(final int requestCode, final NettyRequestProcessor processor,        final ExecutorService executor);    //注册一个默认的处理器,当根据requestCode匹配不到处理器,则使用这个默认的处理器
    void registerDefaultProcessor(final NettyRequestProcessor processor, final ExecutorService executor);    //获取端口
    int localListenPort();    //根据requestCode获取请求处理器
    Pair<NettyRequestProcessor, ExecutorService> getProcessorPair(final int requestCode);    //同步调用(同步发送消息)
    RemotingCommand invokeSync(final Channel channel, final RemotingCommand request,        final long timeoutMillis) throws InterruptedException, RemotingSendRequestException,
        RemotingTimeoutException;    //异步调用(异步发送消息)
    void invokeAsync(final Channel channel, final RemotingCommand request, final long timeoutMillis,        final InvokeCallback invokeCallback) throws InterruptedException,
        RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException;    //单向发送消息,只发送消息。不用处理发送的结果。
    void invokeOneway(final Channel channel, final RemotingCommand request, final long timeoutMillis)
        throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException,
        RemotingSendRequestException;

}
  • 1、registerProcessor 方法
    注册一个处理请求的处理器, 存放到 HashMap中,requestCode为 Map 的 key。
    HashMap<Integer/* request code */, Pair<NettyRequestProcessor, ExecutorService>> processorTable

  • 2、registerDefaultProcessor 方法
    注册一个默认的处理器,当根据requestCode匹配不到处理器,则使用这个默认的处理器

  • 3、invokeSync 方法
    以同步的方式向客户端发送消息。

  • 4、invokeAsync 方法
    以异步的方式向客户端发送消息。

  • 5、invokeOneway 方法
    只向客户端发送消息,而不处理客户端返回的消息。该方法只是向socket中写入数据,而不需要处理客户端返回的消息。

RemotingClient 接口

public interface RemotingClient extends RemotingService {    //更新 NameServer 地址
    void updateNameServerAddressList(final List<String> addrs);    //获取 NameServer 地址
    List<String> getNameServerAddressList();    //同步调用(同步发送消息)
    RemotingCommand invokeSync(final String addr, final RemotingCommand request,        final long timeoutMillis) throws InterruptedException, RemotingConnectException,
        RemotingSendRequestException, RemotingTimeoutException;    //异步调用(异步发送消息)
    void invokeAsync(final String addr, final RemotingCommand request, final long timeoutMillis,        final InvokeCallback invokeCallback) throws InterruptedException, RemotingConnectException,
        RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException;    //单向发送消息,只发送消息。不用处理发送的结果。
    void invokeOneway(final String addr, final RemotingCommand request, final long timeoutMillis)
        throws InterruptedException, RemotingConnectException, RemotingTooMuchRequestException,
        RemotingTimeoutException, RemotingSendRequestException;    //注册一个处理请求的处理器, 根据requestCode, 获取处理器,处理请求
    void registerProcessor(final int requestCode, final NettyRequestProcessor processor,        final ExecutorService executor);    //设置发送异步消息的线程池,如果不设置,则使用默认的
    void setCallbackExecutor(final ExecutorService callbackExecutor);    //获取线程池
    ExecutorService getCallbackExecutor();    //判断 channel 是否可写
    boolean isChannelWritable(final String addr);
}
  • 1、updateNameServerAddressList、getNameServerAddressList 方法
    更新 NameServer 地址。
    获取 NameServer 地址。

  • 2、invokeSync、invokeAsync、invokeOneway 方法
    这三个方法参见 RemotingServer 接口中的方法。

  • 3、setCallbackExecutor
    设置处理异步响应消息的线程池。


服务端和客户端的实现

  • NettyRemotingServer 类实现了RemotingServer 接口

  • NettyRemotingClient 类实现了RemotingClient接口

这两个类使用Netty 来实现服务端和客户端服务的。

NettyRemotingServer 解析

通过 NettyRemotingServer类中的start() 方法开启一个 Netty 的服务端。
代码如下:

    @Override
    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());
                }
            });

        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,                                new HandshakeHandler(TlsSystemConfig.tlsMode))
                            .addLast(defaultEventExecutorGroup,                                //编码
                                new NettyEncoder(),                                //解码
                                new NettyDecoder(),                                //心跳检测
                                new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()),                                //连接管理handler,处理connect, disconnect, close等事件
                                new NettyConnectManageHandler(),                                //处理接收到RemotingCommand消息后的事件, 收到服务器端响应后的相关操作
                                new NettyServerHandler()
                            );
                    }
                });        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);
    }

从 start 方法中启动一个Netty 的服务端。

  • 通过设置的自定义的 NettyEncoder对发送的消息进行编码(序列化)。

  • 通过NettyDecoder 对接收的消息进行解码操作(反序列化)

  • 最后再把反序列化的对象交给 NettyServerHandler 进行处理。

NettyRemotingClient 解析

通过 NettyRemotingClient 类中的 start 方法开启一个 netty 客户端

@Override
    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()),                        //连接管理handler,处理connect, disconnect, close等事件
                        new NettyConnectManageHandler(),                       //处理接收到RemotingCommand消息后的事件, 收到服务器端响应后的相关操作
                        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();
        }
    }

从 start 方法中启动一个Netty 客户端服务。

  • 通过设置的自定义的 NettyEncoder对发送的消息进行编码(序列化)。

  • 通过NettyDecoder对接收的消息进行解码操作(反序列化)

  • 最后再把反序列化的对象交给 NettyServerHandler` 进行处理。

序列化反序列化

通过分析 RemotingServer  和 RemotingClient  接口及实现可以发现,发送消息和接收到的消息都是 RemotingCommand 对象。
经过分析 NettyEncoderNettyDecoder 发现,序列化和反序列化调用的是  RemotingCommand 对象的 encodedecode 方法

消息格式

webp

  • 第一部分是消息的长度,占用4个字节。等于第二、三、四部分长度的总和。

  • 第二部分是消息头的长度,占用4个字节。等于第三部分长度大小。

  • 第三部分是通过Json序列化的消息头的数据。

  • 第四部分是序列化的消息数据。

具体的消息格式我们通过 RemotingCommand类的 encodedecode 方法进行分析。

RemotingCommand.encode() 方法

    public ByteBuffer encode() {        // 1> header length size
        int length = 4;        // 2> header data length
        byte[] headerData = this.headerEncode();
        length += headerData.length;        // 3> body data length
        if (this.body != null) {
            length += body.length;
        }

        ByteBuffer result = ByteBuffer.allocate(4 + length);        // length
        result.putInt(length);        // header length
        result.put(markProtocolType(headerData.length, serializeTypeCurrentRPC));        // header data
        result.put(headerData);        // body data;
        if (this.body != null) {
            result.put(this.body);
        }

        result.flip();        return result;
    }

1、定义消息头的长度为 length = 4
2、通过 this.headerEncode() 获取序列化的 header data。
3、然后申请一个长度为 length + header length + header data +body 大小的ByteBuffer。
ByteBuffer result = ByteBuffer.allocate(4 + length);
4、然后向 ByteBuffer result 中填充数据

headerEncode 方法

该方法主要是实现了消息头的序列化。

private byte[] headerEncode() {        this.makeCustomHeaderToNet();        if (SerializeType.ROCKETMQ == serializeTypeCurrentRPC) {            return RocketMQSerializable.rocketMQProtocolEncode(this);
        } else {            return RemotingSerializable.encode(this);
        }
    }

序列化消息头有两种方式SerializeType.ROCKETMQ 和 SerializeType.JSON。
如果是SerializeType.JSON方式序列化比较简单。

RemotingSerializable.encode 方法

SerializeType.JSON 类型序列化。

    public static byte[] encode(final Object obj) {        final String json = toJson(obj, false);        if (json != null) {            return json.getBytes(CHARSET_UTF8);
        }        return null;
    }



作者:jijs
链接:https://www.jianshu.com/p/eed8f46f8355


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消