前边讲了ByteBuf、Channel、Unsafe、ChannelPipeline、ChannelHandler等核心的类。这篇来学习学习EventLoop(EventLoopGroup)——Netty的线程。Netty的线程模型是经过精心的设计,既提高了框架的并发性能,又能在很大程度上避免死锁,局部还是实现了无锁化设计。非常值得学习的。
一,Reactor线程模型:Netty线程模型本质上也是经典的Reactor线程模型,看下前边转过的一篇文章《Reactor模式详解(转)》。其中包括了,Reactor单线程模型、Reactor多线程模型、Reactor主从多线程模型。大家可以参考Reactor线程模型图进行对比查看。
类型 | 特点 | 不足 |
---|---|---|
Reactor单线程模型 | 1,指所有的I/O操作都是在同一个NIO线程上完成,包括:a,接收客户端的TCP连接;b,向服务端发起TCP连接;c,读取通信对端的请求或应答消息;d,向通信对端发送消息或者应答消息。 2,简单、容易理解并实现 |
1,一个NIO线程同时处理成百上千的链路,性能无法支撑; 2,当NIO线程负载过重,处理速度变慢,导致客户端超时,超时后重发,更加重NIO线程的负载,最终导致大量消息积压和处理超时,成为系统瓶颈; 3,可靠性问题:一旦NIO线程意外,或者进入死循环,会导致整个系统通信模块不可用,造成节点故障。 |
Reactor多线程模型 | 1,有一个专门NIO线程——acceptor线程用于监听服务端,接受客户端的TCP连接; 2,网络IO操作——读写等由一个NIO线程池负责,包含一个任务队列和N个可用线程,由这些NIO线程负责消息的读取、解码、编码和发送等。 3,一个NIO线程可以同时处理N条链路,但是一个链路只对应一个NIO线程,防止发生并发操作问题。 |
1,一个NIO线程负责监听和处理所有的客户端连接可能会存在性能问题。例如百万客户端连接,或者服务端对客户端握手进行安全认证(耗费性能),可能会出现性能不足。 |
主从Reactor多线程模型 | 1,服务端用于接受客户端连接的不再是一个单独的NIO线程,而是一个独立的NIO线程池; 2,sub reactor线程池用来负责SocketChannel的读写、编解码等工作。 |
1,相对于上边两种更加复杂吧。 |
二,Netty线程模型:Netty可以通过配置不同的启动参数,支持上边的几种线程模型的,看下原理图和代码吧:
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap b =new ServerBootstrap();
b.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,100).handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//解码
socketChannel.pipeline().addLast(MarshallingCodeFactory.buildMarshallingDecoder());
//编码
socketChannel.pipeline().addLast(MarshallingCodeFactory.buildMarshallingEncoder());
socketChannel.pipeline().addLast(new SubReqServerHandler());
}
});
ChannelFuture f =b.bind(port).sync();
f.channel().closeFuture().sync();
类别 | 职责 |
---|---|
用于接收Client请求的线程池 | 1,接收Client的TCP连接,初始化Channel参数; 2,将链路状态变更时间通知给ChannelPipeline; |
用于处理I/O操作的线程池 | 1,异步读取通信对端的数据报,发送读时间到ChannelPipeline; 2,异步发送消息到通信对端,调用ChannelPipeline的消息发送接口; 3,执行系统调用Task; 4,执行定时任务Task,例如链路空闲状态监测定时任务。 |
串行操作(无锁化设计):上篇Netty(十二)——ChannelPipeline之观、Netty(十三)——ChannelHandler之意 我们看到事件的处理是在ChannelPipeline中传输像职责链一样,经过ChannelHandler时进行处理,这种启动多个串行化的线程并行运行(避免锁的竞争),比一个队列一个工作线程性能更优。
三,Netty实践建议:
1,创建两个NioEventLoopGroup,用于逻辑隔离NIO Acceptor和NIO IO操作线程;
2,尽量不要在ChannelHandler中启动用户线程(解码后用于将POJO消息派发到后端业务线程的除外);
3,解码要放在NIO线程调用的解码Handler中进行,不要切换到用户线程中完成消息的解码;
4,如果业务逻辑操作简单,没有复杂的业务逻辑计算,没有可能导致线程被阻塞的磁盘操作、数据库操作、网络操作等,可以直接在NIO线程上