Netty技巧

Posted by 皮皮潘 on 11-29,2021
  1. 可以在Pipeline.addLast的时候指定Group从而让业务逻辑(对应的Handler)运行在其他线程池中以避免I/O被阻塞,也即在触发对应Handler的read时直接异步submit线程池,而不是同步调用
  2. 通过继承SimpleChannelInboundHandler并实现对应的channelRead0可以使得Netty能基于传入的数据类型来选择对应的InboundHandler,而不再需要在每个InboundHandler通过if逻辑来判断当前对象是否是本 handler 可以处理的对象,也不用强转,不用往下传递本 handler 处理不了的对象,另外也会自动地释放PooledByteBuf
  3. 可以在channelActive事件发生时调用ctx.executor().scheduleAtFixedRate()来执行定时任务,如:心跳检查等,ctx.executor()其实就是当前Channel的EventLoop
  4. 心跳检查的实现可以通过客户端基于定时任务定时发送心跳,服务端实现IdleStateHandler来检查心跳
  5. 一般通过实现ByteToMessageCodec和ByteToMessageDecoder来实现Byte数组和Java对象之间的互相转换,而不是直接实现ChannelHandler,虽然后者也可以,但是过于麻烦了
  6. 拆包粘包也是基于Decoder来实现的,Netty自带了4种拆包器(FrameDecoder):1.FixedLengthFrameDecoder 2.LineBasedFramDecoder 3.DelimiterBasedFrameDecoder 4.LengthFieldBasedFrameDecoder
  7. 可以通过Channel.config().setWrite[High|Low]WarerMark()来实现背压和限流控制
  8. 在InboundChannelHandler中要记得通过ReferenceCountUtil.release来释放PooledByteBuf,在OutboundChannelHandler中除了要释放ByteBuf之外,如果Promise不继续传递给下一个Handler则需要调用Promise.setSuccess()去通知相关订阅者
  9. AbstractChannelHandlerContext本身继承了DefaultAttributeMap,因此可以使用Context进行一些数据的暂存
  10. 在Server端的一个Channel连接中如果需要与其他Serverl建立连接(服务器作为Proxy),在新创建的Bootstrap中一般都不会从头创建一个EventLoop,而是复用Channel对应的EventLoop
  11. 由于Netty一般都作为转发中间件,因此Netty可以通过发送数据时的水位控制(高低位 + ChannelWritabilityChanged事件 + AutoRead启停)来实现背压功能:当ChannelOutboundBuffer中缓存的数据超过WRITE_BUFFER_HIGH_WATER_MARK时,会设置ChannelWritability为false同时触发Pipeline的ChannelWritabilityChanged事件,此时可以通过配置config.setAutoRead(false)以及channel.attr(READ_SUSPENDED).set(Boolean.valueOf(true))来停止Netty的读取,从而使得读缓冲区变满进而使得TCP滑动窗口变为0从而使得上游服务无法再传输数据给Netty,因此Netty有了充分的时间去将数据写给下游服务,以此实现背压,接下来当ChannelOutboundBuffer中缓存的数据量低于 WRITE_BUFFER_LOW_WATER_MARK时,会设置ChannelWritability为true同时再次触发Pipeline的ChannelWritabilityChanged事件,此时可以通过配置config.setAutoRead(true)以及channel.attr(READ_SUSPENDED).set(Boolean.valueOf(false))并再次调用channel.read()方法重新开启Netty的读取,具体实现可以参考ChannelTrafficShapingHandler(流量整形),其中channel.read()会调用headContext.beginRead()从而注册OP_READ但不会真正地触发read操作
  12. 所有的ByteBuf都应该通过ByteBufAllocator进行分配而不是手动创建,该Allocator可以通过ChannelContext#alloc()方法得到,同时该Allocator是全局唯一的(单例模式),整个Netty的内存分配都由它负责,另外在DefaultChannelConfig中还有一个RecvByteBufAllocator,它只是单纯地用来接收输入数据的,而不是用作ByteBuf分配的