​结合异步模型,再次总结Netty多线程编码最佳实践

更多技术分享可关注我

前言

本文重点总结Netty多线程的一些编码最佳实践和注意事项,并且顺便对Netty的线程调度模型,和异步模型做了一个汇总。原文:​​结合异步模型,再次总结Netty多线程编码最佳实践

Netty多线程编码的最佳实践总结

接该文:Netty的线程调度模型分析(10)《Netty多线程开发的最佳实践有哪些?》

回忆:

1、服务端需要启动两个NioEventLoopGroup,其中boss(新连接接入)线程池大小设置为1即可,设置多了也是1个I/O线程在起作用,而且还浪费内存。

2、如果业务非常简单,执行时间非常短,不需要与外部网元交互、比如访问数据库等,也不需要等待其它资源,那么建议直接在业务ChannelHandler中执行,不需再启动Netty的非I/O线程池或者使用额外的线程池,避免大量CPU上下文切换的开销,而且也不存在线程安全问题

3、如果业务逻辑耗时较大,或者是时间不可控的业务,比如查询数据库,那么建议封装为task后,投递到后端的非I/O线程池统一处理,可以使用Netty默认提供的无锁串行化线程池——DefaultEventExecutorGroup,在添加handler的时候绑定即可,或者直接投递到另外的进程中处理,比如消息队列,最后把计算结果发送给Netty的I/O线程即可,如果使用了非I/O线程驱动耗时逻辑,那么再传递结果时,Netty的I/O线程会判断当前线程是不是I/O线程,如果不是,那么Netty会自动将该逻辑封装为task扔到MPSCQ,并让I/O线程驱动,因此不用担心业务线程池的结果无法返回到I/O线程

4、过多的业务ChannelHandler会带来开发效率和可维护性问题,不要把Netty当作业务容器,对于大多数复杂的业务产品,仍然需要集成或者开发自己的业务容器,做好和Netty的架构分层。

额外补充几点:

1、善于使用Netty的钩子方法:参考:Netty的异步模型分析(5)

2、尽量不要在ChannelHandler中启动用户线程(解码消息并派发消息到非I/O线程池除外)

3、解码处理器应该被NIO线程调用,不要切换到用户线程

4、一个EventLoop在它的整个生命周期当中都只会与唯一一个永远不会被改变的Thread进行绑定,所有由EventLoop处理的I/O事件都将在它所关联的那个Thread上进行处理,一个Channel在它的整个生命周期中只会注册在一个EventLoop上,一个EventLoop在运行过程当中,会被分配给一个或者多个Channel,得出重要结论:在Netty中,Channel的实现一定是线程安全的,基于此,用户可以存储一个Channel的引用,并且在需要向远程建点发送数据时,通过这个引用来调用Channel相应的方法,即便当时有很多线程都在使用它也不会出现线程不安全问题,而且消息一定会按顺序发出去。

5、千万理解Netty的NIO线程的模型,它的样子,如果是异步API被NIO线程驱动,那么该API本质还是串行的,因为NIO线程是异步串行无锁化模型,要想实现非串行执行,必须将异步API封装为task,让非I/O线程驱动。当然对于简单的收发消息,大可不必这样笨拙,大胆的让NIO线程驱动即可。具体参考:Netty的writeAndFlush()异步实现源码分析和正确用法

6、可以使用重载的addLast方法,在向pipeline添加handler时,传入Netty提供的非I/O线程池——DefaultEventExecutor,此后该handler上的事件,都是传入的group线程池来执行。具体参考:Netty耗时的业务逻辑应该写在哪儿,有什么注意事项?

7、强烈建议使用Netty的异步API时,都为其附加一个Netty的监听器,监听异步I/O的结果,尽量少用异步转同步API,即NIO线程能不阻塞就不阻塞。

8、如果要使用异步转同步API,那么必须使用非NIO线程驱动,否则会死锁,具体原因见:Netty异步API转同步的实现原理和正确用法

个人阶段性感想暨对Netty的认识总结

从18年年中接触Netty,读过《Netty实战》,《Netty权威指南》,《跟着案例学Netty》,以及闪电侠的源码分析课程,直到自己通过demo入手通读了它的源码,到现在已经快两年了,期间搞过Netty的小轮子,比如仿微信聊天服务器,HTTP长连接客户端,简单的分布式RPC框架,有了这些经历才催生了这一系列笔记文章。

水平一般,能力有限,就我个人理解,对Netty的抽象如下:

理解Netty的核心就是4个图像,或者说模型也OK,之所以我现在叫它们图像,是因为将这些机制想象抽象为图形,比较容易理解和加深印象,分别是:

1、线程调度图像

即对epoll机制,NIO三大组件(I/O多路复用器Selector+Channel+Buffer)和基本的网络编程流程,Reactor模型,和Java多线程编码的把握。首先,需要知道BIO和NIO的区别,NIO三大件的特点和用途,原生NIO的bug,比如臭名昭著的epoll空轮询bug、还有一些坑,比如处理I/O事件需要移除key,I/O多路复用器(Selector)返回的集合不是线程安全的,如何正确的给Selector注册Channel和更新感兴趣的I/O事件,如何正确的处理写事件,如何正确的处理连接事件,如何正确的取消注册Channel等,太多了,以致于能深刻理解为何Netty会这么火。之后就是对Reactor三种模型的正确理解,尤其是主从模型的理解,以及Netty默认使用的是多线程Reactor模型。然后就是对JUC的使用,常见无锁工具的理解,如何配置I/O线程池,Netty的NIO线程的事件循环机制等,最后就是对Linux的5种I/O模型的认识,尤其是I/O多路复用模型中的epoll机制的深刻把握。

对应Netty就是Channel,Unsafe,EventLoop(Group)组件。

2、异步图像

即把握对观察者设计模式,多线程设计模式中的Future,承诺模式的理解,还有JUC的管程,锁的机制的理解等。

对应Netty就是Future和Promise组件。

3、流水线图像(pipeline+事件回调图像)

这是后续要分析总结的pipeline模型和事件回调机制,这部分需要知道Netty的pipeline模型和职责链设计模式的区别,pipeline的双向链表结构,入站和出站处理器的设计理念和各个回调事件的正确使用,还有一些Netty已经实现好的常用的入站,出站处理器的正确使用和理解。

对应Netty就是ChannelPipeline和ChannelHandler组件

4、内存图像

也是后续要分析总结的,核心就是把握Netty的ByteBuf设计理念,和NIO的Buffer的缺陷,重点是ByteBuf的内存池设计思想,Netty垃圾回收计数器的设计和编码注意事项,堆外内存的使用套路,这部分重要程度和线程调度模型并驾齐驱,甚至还在之上,可以说深刻掌握了内存图像才算掌握了Netty,需要知道Netty有哪些内存类型,不同类型不同大小的内存是如何分配的,不同类型内存的回收策略,如何避免OOM,如何减少多线程对内存分配的竞争,所谓的Netty的零拷贝和操作系统的零拷贝的区别等。

以上,掌握了这几个图像,个人认为Netty就应该算掌握的比较不错了,剩下的就是对Netty的高性能工具类的学习和对其实现源码的把握,比如时间轮工具——HashedWheelTimer,改进的FastThreadLocal,内存池,常量池,@Sharable注解在何时可以使用以及坑。

进一步,需要掌握Netty的一些高级特性的实现原理和用法,比如空闲检测handler,并且知道如何设计长连接以及对应的心跳包和应用层心跳的必要性,知道如何断链重连,如何设计重复登录保护机制,还要知道Netty所提供的TCP协议的粘包半包处理器的使用方法和底层原理,流控和流量整形的使用方法,并且知道流控和流量整形的区别以及各自的实现机制。

再进一步,就是熟悉各种开源框架中如何使用的Netty,比如Zookeeper,Kafka,Elasticsearch,gRpc等开源框架是怎么用Netty的。

个人应该正处在上个阶段。最后再往下钻只能先抛砖引玉,因为可能单纯靠看源码或者文章会力不从心,至少我是这样感觉的,所以需要进一步用实际的高并发项目打磨。比如IM项目,游戏服务器,推送服务等,这样应该能更深刻的理解比如私有协议的一般设计套路,压缩合并handler的技巧,单机百万长连接的调优过程,即著名的C10K到C10M问题,Netty的性能指标和监控策略,需要对Linux操作系统和TCP协议有一定认识,比如常用的TCP参数的深入理解,可以通过如下几个问题,也是我收集的很常见的面试题来自我评判,后续专题总结出来:

1、TCP协议如何保证可靠性

2、TCP协议如何对发送的数据进行分段?

3、TCP协议有几种状态,都是什么,是如何转换的?

4、三次握手中,如果服务端TCP状态为SYN_RECEV态,此时发普通数据包给服务器,服务器会怎么处理?

5、TCP连接何时会进入TIME-WAIT态,该状态会如何转移?

6、TCP四次分手,最后为什么要等待2MSL后才关闭连接,为何不是4MSL或者其它时间?

7、TCP连接的关闭过程由于存在TIME-WAIT态,这会影响其他服务器程序在该端口建立TCP连接吗?

8、TCP被动关闭方如果总收不到对端最后一个ACK,那会一直重传FIN段么?

9、TCP主动关闭方有没有可能等待2MSL后,收到对端的超时重传FIN报文?

10、如何理解IP数据报的TTL?

11、TCP的被动关闭端为什么不需要类似TIME_WAIT的状态?

12、什么是TCP的全连接,半连接队列?

13、如何关闭TCP连接,怎么优雅的关闭?

14、为什么TCP有一个SO_REUSEADDR参数,它对网络编程有什么影响?

15、服务器TIME_WAIT状态过多怎么办,如何定位并解决?

16、TCP的RST标志位在何时会出现?

17、TCP的RST攻击是怎么回事?

18、Java的IOException:Connection reset by peer的真正原因是什么?

19、TCP的SYN Flood攻击是怎么回事,如何解决?

20、TCP中已有SO_KEEPALIVE选项,为什么还在应用层加入心跳机制?

21、TCP如何处理小块的数据流(涉及nagle算法)?

22、TCP如何处理大块数据流(涉及滑动窗口,拥塞控制算法)?

23、TCP协议的7个定时器是哪几个,分别在什么条件下起作用?

24、TCP协议的应用层的粘包、分包问题和解决方案

25、TCP协议存在哪些缺陷?

26、TCP/IP和HTTP的区别和联系?

27、一个应用最多可以支持多少TCP并发连接?

28、单机百万长连接的调优过程,涉及操作系统的一些参数+TCP参数+Netty参数+JVM参数的配置

29、某个应用的CPU使用率很高,甚至达到100%,应该怎么处理?

30、什么是僵尸进程,大量的僵尸进程或者不可中断进程该怎么处理?

31、如何分析CPU的瓶颈?

32、服务器的内存swap变高了应该怎么处理?

33、JVM发生了OOM该怎么定位和处理,有哪些命令和工具?

34、。。。

以上,篇幅有限,想到哪儿写到哪儿,关于Netty的线程调度,I/O多路复用器,异步API的拆解算是告一段落,接下来的几篇文章分析总结的是Netty服务端新连接接入的过程和Netty的pipeline+事件回调机制。

4句诗与君共享:

1、问渠那得清如许,为有源头活水来

2、沉舟侧畔千帆过,病树前头万木春

3、革命尚未成功,同志仍需努力

4、低头做事,抬头看路,既要深入细节,又要跳出来看全局

后记

dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

原文地址:https://www.cnblogs.com/kubixuesheng/p/12658184.html