读书笔记:《Netty进阶之路》——ChannelHandler并发安全、NioEventLoop防阻塞挂死

  《Netty进阶之路》第7章、第8章分别提出ChannelHandler并发安全问题,NioEventLoop线程阻塞导致消息接收和处理缓慢。ChannelHandler的并发安全问题很好分析,NioEventLoop线程阻塞则需要一些技巧。
  

1 ChannelHandler并发安全

  默认每个Channel中有各自的ChannelHandler实例,因此如果所有业务在NioEventLoop中完成则没有线程安全问题,因为从消息接收到响应写出阶段都由同一个NioEventLoop线程处理;若在ChannelHandler消息处理过程中新开启一个线程,则新线程和NioEventLoop同时读写ChannelHandler中的变量时会有线程不安全的风险。

@Component(value = "gatewayChannelInitializer")
public class GatewayChannelInitializer extends ChannelInitializer {
    private static final String ENCODER = "encoder";
    private static final String DECODER = "decoder";

    @Autowired
    private BusyMan busyMan;

    protected void initChannel(Channel channel) throws Exception {
        // 每个Channel一个GatewayChannelHandler实例
        GatewayChannelHandler gatewayChannelHandler = new GatewayChannelHandler();
        gatewayChannelHandler.setBusyMan(busyMan);

        ChannelPipeline pipeline = channel.pipeline();

        pipeline.addLast(ENCODER, new HttpResponseEncoder());
        pipeline.addLast(DECODER, new HttpRequestDecoder());
        pipeline.addLast(gatewayChannelHandler);
    }
}

  如果ChannelHandler像下面这样用@Sharable标注LauncherChannelHandler 为共享实例,则多个NioEventLoop共用一个LauncherChannelHandler实例,它们并发访问ChannelHandler,此时ChannelHandler的变量被并发读写,存在线程不安全的风险。

import io.netty.channel.ChannelHandler.Sharable;

@Sharable
public class LauncherChannelHandler extends ChannelInboundHandlerAdapter {

2 NioEventLoop高性能

  8.2.4的总结是,要防止NioEventLoop阻塞挂死,最佳实践是:①两个以上NioEventLoop线程构成Reactor模式(Netty服务端模式会自动构建Reactor模式);②尽量不要在NioEventLoop线程中新开线程;③解码不要新开线程;④业务如果耗时很短,不要新开线程;⑤耗时操作不要放在NioEventLoop执行,必须新开线程执行,最好利用线程池和生产者消费者模式。
  第①⑤点很好理解,第②③④在说同一个事情:如果耗时很短,不要自己开启新线程异步处理,因为新开启线程的目的是利用多核优势节省时间,而耗时很短的运算新开线程收益不大。那就是说,是否开启新的线程取决于“异步带来的时间收益”和“多线程切换开销”之间的权衡。
  NioEventLoop从SingleThreadEventLoop继承了一个任务队列Queue tailTasks,SingleThreadEventLoop从SingleThreadEventExecutor继承了一个任务队列Queue taskQueue和线程池Executor executor,在这个线程池中首先启动模板方法run()(实现方法在NioEventLoop中:一个for循环处理所有socket事件),SingleThreadEventExecutor从AbstractScheduledEventExecutor继承了一个带优先级的定时任务队列PriorityQueue> scheduledTaskQueue。因此,在NioEventLoop中最好不要自己新开线程执行异步任务和定时任务,NioEventLoop已经具备这些基础设置。

你可能感兴趣的:(Netty)