netty的线程模型
1. bossGroup线程组
如果绑定了一个端口的话,那么只有一条线程来处理所有到来的请求.即使这个线程组里可能不止一条线程 如果绑定了两个端口的话,那就有两条线程 new EventLoopGroup(1) 所以, 可以指定线程组之初始化1条线程
所以, ServerBootstrap监听一个端口对应一个boss线程,它们一一对应.
2. worker线程组
在boss接受了socket连接请求后,会产生一个channel,并把这个channel交给ServerBootstrap初始化时指定的ServerSocketChannelFactory处理, boss线程则继续处理socket的请求
ServerSocketChannelFactory则会到worker线程池中找出一条worker线程来继续处理这个请求
**如果是OioServerSocketChannelFactory,**那么这个channel上所有的socket消息,从开始到channel(socket)关闭,都只由这个特定的worker线程来处理,也就是说打开的一个socket对应一个指定的worker线程,在这个socket没有关闭的情况,只能为这个socket处理消息,没有办法为其他socket服务
** 如果是NioServerSocketChannelFactory,** 则不然.每个worker可以服务不同的socket或者channel,也就是说,channel和worker不再是一一对应的关系.
** 总结上面,NioServerSocketChannelFactory 只需要少量活动的worker线程,就能处理好众多的 channel,而OioServerSocketChannelFactory 必须要是打开和channel等量的worker线程来服务.**
线程是一种资源,所以netty服务器需要处理长连接的时候,最好选择NioServerSocketChannelFactory,这样可以避免创建大量的worker线程. 在用作http服务器的时候,最好也选择NioServerSocketChannelFactory,
3. worker线程的生命周期
** inBound的handler 和 outBound的handler是不是在一个线程里? 为什么我测试的可能在一个线程,也可能不在一个线程 ** 下面这段话来源于网络,似乎和我测试的不符.
对 于Nio当messageReceived()方法执行后,如果没有产生异常,worker线程就执行完毕了,它会被线程池回收。业务逻辑hanlder 会通过一些方法,把返回的数据交给指定好顺序的DownStreamHandler处理,处理后的数据如果需要,会被写入channel,进而通过绑定的 socket发送给客户端。这个过程是由另外一个线程池中的worker线程来完成的。
4.减少worker线程的处理占用时间
worker线程是由netty内部管理, 统一调配的一种资源,所以最好应该尽快的让worker线程执行完毕,返回给线程池回收利用.
** worker线程的大部分时间都消耗在ChannelPipeline的的各种handler中, 而在这些handler中,一般是负责应用程序逻辑渗入的那个handler最耗时间,他通常是排在最后的handler.所以把这部分处理内容交给另外一个线程来处理,可以有效的减少worker线程的周期循环时间. **
一般有两种方法:
- new Thread().start(), 而worker线程执行完messageReceived 就结束了
- 利用 netty框架自带的ExecutionHandler
public class DatabaseGatewayPipelineFactory implements ChannelPipelineFactory {
private final ExecutionHandler executionHandler;
public DatabaseGatewayPipelineFactory(ExecutionHandler executionHandler) {
this.executionHandler = executionHandler;
}
public ChannelPipeline getPipeline() {
return Channels.pipeline(
new DatabaseGatewayProtocolEncoder(),
new DatabaseGatewayProtocolDecoder(),
executionHandler, // 多个pipeline之间必须共享同一个ExecutionHandler
new DatabaseQueryingHandler());//业务逻辑handler,IO密集
}
}
把 共享的ExecutionHandler实例放在业务逻辑handler之前即可,注意ExecutionHandler一定要在不同的pipeline 之间共享。它的作用是自动从ExecutionHandler自己管理的一个线程池中拿出一个线程来处理排在它后面的业务逻辑handler。而 worker线程在经过ExecutionHandler后就结束了,它会被ChannelFactory的worker线程池所回收。
它的构造方法是ExecutionHandler(Executor executor) ,很显然executor就是ExecutionHandler内部管理的线程池了。netty额外给我们提供了两种线程池: MemoryAwareThreadPoolExecutor和OrderedMemoryAwareThreadPoolExecutor,它们都在org.jboss.netty.handler.execution 包下。 MemoryAwareThreadPoolExecutor 确保jvm不会因为过多的线程而导致内存溢出错误,OrderedMemoryAwareThreadPoolExecutor是前一个线程池的子类,除 了保证没有内存溢出之外,还可以保证channel event的处理次序
上面的ExecutionHandler似乎在Netty5.0里废除了 OrderedMemoryAwareThreadPoolExecutor 和 MemoryAwareThreadPoolExecutor 也被废除了
注意:
执行整个链是串行的, 如果业务逻辑比较耗时, 比如读取数据库,会导致worker线程被长期占用而得不到释放,最终影响的是整个服务器端的处理效率,所以,我们把最后那个处理业务逻辑的handler放一个线程池里执行,让worker即使释放.
ctx.executor().schedule
ctx.channel().eventLoop().schedule
上面两个线程都是从workerGroup里拿出来的
思考:那么我们想要执行一些耗时的操作时,应该自己管理一个线程池,还是用上面的这两个方法拿到线程呢?