RocketMQ如何使用Netty
认知有限,难免误差,持续完善
Apache RocketMQ 自诞生以来,因其架构简单、业务功能丰富、具备极强可扩展性等特点被众多企业开发者以及云厂商广泛采用。历经十余年的大规模场景打磨,RocketMQ 已经成为业内共识的金融级可靠业务消息首选方案,被广泛应用于互联网、大数据、移动互联网、物联网等领域的业务场景。
NettyRemotingServer
这是RocketMQ网络连接服务端,我们重点看看constructor、buildEventLoopGroupBoss、buildEventLoopGroupSelector、initServerBootstrap、NettyServerHandler中的一些细节
package org.apache.rocketmq.remoting.netty;
......
public NettyRemotingServer(final NettyServerConfig nettyServerConfig,final ChannelEventListener channelEventListener) {......this.serverBootstrap = new ServerBootstrap();this.nettyServerConfig = nettyServerConfig;this.channelEventListener = channelEventListener;
this.publicExecutor = buildPublicExecutor(nettyServerConfig);this.scheduledExecutorService = buildScheduleExecutor();
this.eventLoopGroupBoss = buildEventLoopGroupBoss();this.eventLoopGroupSelector = buildEventLoopGroupSelector();
loadSslContext();
}
buildEventLoopGroupBoss
与Dubbo类似,根据使用操作系统环境,选择不同的EventLoopGroup实现,也都是明确指名只使用一个线程,但这里使用的用户线程非守护。这里也都为线程起了一个有意义的名字。
protected EventLoopGroup buildEventLoopGroupBoss() {if (useEpoll()) {return new EpollEventLoopGroup(1, new ThreadFactoryImpl("NettyEPOLLBoss_"));} else {return new NioEventLoopGroup(1, new ThreadFactoryImpl("NettyNIOBoss_"));}
}
补充知识一:NioEventLoopGroup与EpollEventLoopGroup
NioEventLoopGroup是一种通用实现,各个操作系统均可用,EpollEventLoopGroup是Netty特为Linux系统实现的(直接通过JNI实现),性能高、更丰富的参数配置、更少的GC。
If you are running on linux you can use EpollEventLoopGroup and so get better performance, less GC and have more advanced features that are only available on linux.
Java NIO根据操作系统不同提供了不同实现,比如 macosx 是KQueueSelectorProvider、windows有WindowsSelectorProvider、Linux有EPollSelectorProvider (Linux kernels >= 2.6,是epoll模式)或PollSelectorProvider(selector模式)。而Netty的NioEventLoopGroup,在Linux上时底层就是使用的JDK的EPollSelectorProvider 实现
补充知识二:用户线程与守住线程
特性 | 用户线程 | 守护线程 |
---|---|---|
生命周期 | 独立,JVM 会等待其执行完毕。 | 依赖于用户线程。随用户线程结束而强制结束。 |
目的 | 执行程序的核心业务逻辑。 | 为用户线程提供辅助服务(如GC、监控)。 |
JVM 退出 | 用户线程存,不会退出。 | 会在所有用户线程结束时,强制终止所有守护线程并退出。 |
默认值 | 默认创建的线程都是用户线程。 | 需要通过 setDaemon(true) 显式设置。 |
优先级 | 高 | 低 |
使用场景 | 主要工作线程,如处理请求、计算任务。 | 后台服务,如垃圾回收、日志管理、心跳检测。 |
buildEventLoopGroupSelector
与buildEventLoopGroupBoss不同的是,在线程数据上这里不再是指定1了,而通过配置(nettyServerConfig)来确定,默认是3,所Dubbo有点不同。
protected EventLoopGroup buildEventLoopGroupSelector() {......return new EpollEventLoopGroup(nettyServerConfig..., new ThreadFactoryImpl("..."));......return new NioEventLoopGroup(nettyServerConfig..., new ThreadFactoryImpl("..."));
}
initServerBootstrap
连名字都与Dubbo一样,配置了哪些参数,为我们自己的项目作一些参加,但如果什么都不配置,就是满足需求了,不配置,不修改Netty默认配置是最好的。
protected void initServerBootstrap(ServerBootstrap serverBootstrap) {serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector).channel(useEpoll() ? ....)//当有新的连接请求到达时,如果服务器正忙,这些请求会被放入一个队列中等待处理,//如果队列已满,新的连接请求可能会被拒绝.option(ChannelOption.SO_BACKLOG, 1024)//允许地址重用,即端口占了,也能启动。在紧急重启时有用。.option(ChannelOption.SO_REUSEADDR, true)//不开启的keepalive.childOption(ChannelOption.SO_KEEPALIVE, false)//禁用Nagle算法,禁用后,小数据包不会被缓冲和合并,数据会立即发送,不等待.childOption(ChannelOption.TCP_NODELAY, true).localAddress(new InetSocketAddress(this.nettyServerConfig.getBindAddress(),this.nettyServerConfig.getListenPort())).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) {//这里会到NettyServerHandlerconfigChannel(ch);}});
addCustomConfig(serverBootstrap);
}
NettyServerHandler
在initServerBootstrap中最后会在ChannelPipeline中添加NettyServerHandler,这与Duboo一致,在这里的核心业务处理器,是一个ChannelDuplexHandler(同时是Inbound、Outbound)实现。这里我们重点看看channelRead0方法。
protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) {......remotingAbstract.processMessageReceived(ctx, msg);......
}
这里的processMessageReceived -> processRequestCommand :
public void processRequestCommand(final ChannelHandlerContext ctx...) {......final Pair<NettyRequestProcessor, ExecutorService> matched = ...final Pair<NettyRequestProcessor, ExecutorService> pair = .........
}
这里与Doubbo一样,执行放到了线程池里面,Netty默认情况在Handler的执行是与Eventloop同一个线程,当某一个Channel的执行阻塞是整个Eventloop(一个线程中启了一个死循环)就会阻塞,影响Eventloop监控的其他Channel的处理。所以对于业务复杂、耗时的处理建议都放到一个单独的业务线程池处理。
架构思想
关注点分离,职责不同分配的资源也不同,如BossGroup、WorkerGroup以及Worker Threads,他们都是因为职责不同,分配的线程数就不同。
网格编程
使用Netty,我们要写的网络相关的代码比较小,基本上就是上面的固定结构。
使用Netty,我们要设置的参数也较少,虽然提供了很多,但默认的基本满足大部分功能与非功能要求。
作用Netty,可以像它一样使用EpollEventLoopGroup。
使用Netty,可以把一些参数做成外化配置,方便随时调整。
在主从Reactor多线程模型中,MainReactor一般情况只需要一个线程即可,因为有连接来了,他也只是创建一个SocketChannel,然后工作就交给SocketChannel。相当于一个前台迎宾工作,有人来,请进即可。
在主从Reactor多线程模型中,当业务处理复杂时,需要单独创建Worker Threads,防止EventLoop阻塞。