继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

RocketMQ 底层通信机制 源码分析

慕的地10843
关注TA
已关注
手记 1081
粉丝 200
获赞 962

概述

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


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP