更多技术分享可关注我

前言

本文重点总结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电子书,资料,帮忙内推,欢迎拍砖!

​结合异步模型,再次总结Netty多线程编码最佳实践的更多相关文章

  1. 10个精妙的Java编码最佳实践

    这是一个比Josh Bloch的Effective Java规则更精妙的10条Java编码实践的列表.和Josh Bloch的列表容易学习并且关注日常情况相比,这个列表将包含涉及API/SPI设计中不 ...

  2. Windows Azure 安全最佳实践 - 第 7 部分:提示、工具和编码最佳实践

    在撰写这一系列文章的过程中,我总结出了很多最佳实践.在这篇文章中,我介绍了在保护您的WindowsAzure应用程序时需要考虑的更多事项. 下面是一些工具和编码提示与最佳实践: · 在操作系统上运行 ...

  3. 你知道吗?10个精妙的 Java 编码最佳实践

    这是一个比Josh Bloch的Effective Java规则更精妙的10条Java编码实践的列表.和Josh Bloch的列表容易学习并且关注日常情况相比,这个列表将包含涉及API/SPI设计中不 ...

  4. java并发编程笔记(九)——多线程并发最佳实践

    java并发编程笔记(九)--多线程并发最佳实践 使用本地变量 使用不可变类 最小化锁的作用域范围 使用线程池Executor,而不是直接new Thread执行 宁可使用同步也不要使用线程的wait ...

  5. PyTorch模型加载与保存的最佳实践

    一般来说PyTorch有两种保存和读取模型参数的方法.但这篇文章我记录了一种最佳实践,可以在加载模型时避免掉一些问题. 第一种方案是保存整个模型: 1 torch.save(model_object, ...

  6. python编码最佳实践之总结

    相信用python的同学不少,本人也一直对python情有独钟,毫无疑问python作为一门解释性动态语言没有那些编译型语言高效,但是python简洁.易读以及可扩展性等特性使得它大受青睐. 工作中很 ...

  7. 【转载】干货再次来袭!Linux小白最佳实践:《超容易的Linux系统管理入门书》(连载八)用命令实现批量添加用户

    Windows添加用户需要至少5个界面,而Linux一条命令就搞定了,这是不是高效人士办公第一法则呢.本文不给你一堆参数和选项,不让你见识教条主义,只给你最实用的代码. 想每天能听到小妞的语音播报,想 ...

  8. 编码最佳实践——Liskov替换原则

    Liskov替换原则(Liskov Substitution Principle)是一组用于创建继承层次结构的指导原则.按照Liskov替换原则创建的继承层次结构中,客户端代码能够放心的使用它的任意类 ...

  9. Java多线程并发最佳实践

    使用本地变量 尽量使用本地变量,而不是创建一个类或实例的变量. 使用不可变类 String.Integer等.不可变类可以降低代码中需要的同步数量. 最小化锁的作用域范围:S=1/(1-a+a/n) ...

随机推荐

  1. jquery 的animate 的transform

    $(function(){ var t = 1000; $("#id").animate( {borderSpacing:180}, //180 指旋转度数 { step: fun ...

  2. 第八章、小节二vuex

    a.用vuex首先先安装vuex npm install vuex --save b.在src目录下创建store文件夹,在store中创建index.js存放各个状态 c.在一个模块化的打包系统中, ...

  3. python学习的新篇章--面向对象

    面向对象的学习笔记   关键要素: 类:class 用来描述具有相同的属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法.   数据成员: 类的不同属性数据   对象: 类的一个实例 ...

  4. MATLAB神经网络(4) 神经网络遗传算法函数极值寻优——非线性函数极值寻优

    4.1 案例背景 \[y = {x_1}^2 + {x_2}^2\] 4.2 模型建立 神经网络训练拟合根据寻优函数的特点构建合适的BP神经网络,用非线性函数的输入输出数据训练BP神经网络,训练后的B ...

  5. css中:link和@import的区别

    两者都是外部引用css的方式.但是有一定的区别: 1. 从属关系:link是一个xhtml标签,除了加载css外,还可以定义 RSS.rel 连接属性等: @import属于css范畴,只能加载css ...

  6. Scrapy同时启动多个爬虫

    1. 在项目文件夹中新建一个commands文件夹 2. 在command的文件夹中新建一个文件 crawlall.py 3.在crawlall.py 中写一个command类,该类继承 scrapy ...

  7. 初识Flask、快速启动

    目录 一.初识Flask 1.1 什么是flask? 1.2 为什么要有flask? 二.Flask快速启动 一.初识Flask 1.1 什么是flask? Flask 本是作者 Armin Rona ...

  8. 【TIJ4】照例,每个分类的第一篇文章随便说两句

    [其实没啥好说的,完].... 嘛,其实本来也就是放练习的地方. 如果说世界上的课本按练习难度分成两类,一类是像Weiss那种习题比内容难的,还有就是TIJ这种讲得详尽但是习题相对简单的了吧. 不过不 ...

  9. [Linux][C][gcc] Linux GCC 编译链接 报错ex: ./libxxx.so: undefined reference to `shm_open'

    本人原创文章,文章是在此代码github/note的基础上进行补充,转载请注明出处:https://github.com/dramalife/note. 以librt丶用户自定义动态库libxxx 和 ...

  10. Fiddler1 简单使用

    1.Fiddler下载地址:https://www.telerik.com/download/fiddler 2.Fiddler设置: Fiddler是强大的抓包工具,它的原理是以web代理服务器的形 ...