写点什么

netty 的线程模型, 调优 及 献上写过注释的源码工程

  • 2021 年 11 月 11 日
  • 本文字数:2344 字

    阅读完需:约 8 分钟

Reactor 线程模型#

Reactor 线程模型, 顾名思义就像核反应堆一样, 是一种很劲爆的线程模型


最经典的两种图就像下面这样



上面两种图所描述的都是 Reactor 线程模型


Reactor 线程模型五大角色#


  • handle


handle 本质上表示是一种资源 , 在不同的操作系统上他们的名称又不一样, 比如在 windows 上成它为文件句柄 , 而在 linux 中称它为文件描述符, 其实我这样说, handle 的概念就显得比较抽象 不容易理解 , 具体一点说 handle 是啥呢 ? 比如客户端向服务端发送一个连接请求,这个 socket 请求对操作系统来说, 本质上就是 handle


**在 Reactor 线程模型中的 Handle 就是限制 netty 并发量的主要原因, 下面的调优主要也是为了突破这个限制


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


**


  • Synchronius Event Demultiplexer


意为同步事件分离器, 也是一看这个名字完全没有头绪它是什么, 其实, 在本质上它是一个操作系统层面的系统调用, 操作系统用它来阻塞的等待事件的发生, 具体一点它来说, 比如它可以是 Linux 系统上的 IO 多路复用, select(), 或者是 poll(),epoll() , 或者是 Nio 编程模型中的 selector, 总之, 它的特点就是阻塞的等待事件的发生


  • EventHandler


事件处理器, 事件处理器是拥有 Handle 的, 我们可以直观将将 EventHandler 理解成是当系统中发生了某个事件后,?针对这个事件进行处理的回调, 为啥说是回调, 结合 netty 的实现中, 我们在启动 netty 前, 会往他的 pipeline 中添加大量的 handler ,这些 handler 的地位其实和 EventHandler 的地位相当


  • concrete EventHandler


顾明思议, 具体的事件处理器的实现, 换句话说, 这是我们根据自己的需求, 不同的业务逻辑自己去添加上去的处理器


  • InitiationDispacher


初始分发器, 它其实就是整个编程模型的核心, 没错, 他就是 Reactor, 具体怎么理解这个 Reactor , 比如我们就能把他看成一个规范, 由它聚合, 支配其他的四大角色之间有条不紊的工作, 迸发出巨大的能量


Reactor 线程模型五大角色的关系与工作流程#


首先: 我们需要将 EventHandler 注册进 Reactor, 通过上图也能看到, EventHandler 拥有 操作系统底层的 Handle,


并且, Reactor 要求, 当 EventHandler 注册进自己时, 务必将他关联的 handle 一并告诉自己, 由 Reactor 统一进行维护


我们将上面所说的 handle , 联想成 Nio 编程模型中的将 channel 注册进 Selector 时刻意,也是必须绑定上的这个感性趣的事件, 牢记, Reactor 是的这个模型的核心, 待会操作一旦系统发现 handle 有了某些变动,需要由 Reactor 去通知具体哪个 EventHandler 来处理这个资源. 如何找到正确的 EventHandler 依赖的就是这个 handle , 或者说它是感兴趣的事件


更近一步: 当所有的 EventHandler 都注册进了 Reactor 中后, Reactor 就开始了它放纵的一生, 于是它开始调与 同步事件分离器通信 ,企图等待一些事件的发生, 什么事件呢? 比如说 socket 的连接事件


当同步事件分离器发现了某个 handle 的状态发生了改变, 比如它的状态变成了 ready, 就说明这个 handle 中发生了感兴趣的事件, 这时, 同步事件分离器会将这个 handle 的情况告诉 Reactor , Reactor 进一步用这个 handle 当成 key, 获取出相对应的 EventHandler 开始方法的回调...

如何实现单机百万性能调优#

? 当我们进行 socket 编程时, 我们得给 Server 端绑定上一个端口号, 客户端一般会被自动分配 Server 所在的机器上的一个端口号, 区间一般是 1025-65535 之间, 这样看上去, 即使服务器的性能再强, 即使 netty 再快, 并发数目都被操作系统的特性限制的死死的


突破局部文件句柄的限制#


? 像 windows 中的句柄或者是 linux 的文件描述符 这种能打开的资源的数量是有限的, 每一个 socket 连接都是一个句柄或者是描述符, 换句话说, 这一个特性限制了 socket 连接数, 也就限制了并发数


查看 linux 系统中一个进程能打开的单个文件数,(它限制了单个 jvm 能打开的文件描述符数,每一个 tcp 连接对 linux 来说都是一个文件)


Copy


ulimit -n


修改这个限制:


Copy


# 在 /etc/security/limits.conf 追加以下内容 , 表示任何用户的链接最多都能打开100万个文件 * hard nofile 100000 * soft nofile 100000


重启机器生效:


突破全局文件句柄的限制#


查看当前系统中的全局文件句柄数


Copy


cat /proc/sys/fs/file-max


修改这个配置


Copy


# 在 /etc/sysctl.conf 中追加如下的内容 fs.file-max = 1000000


虚拟机参数的经验值#


Copy


# 堆内存 -Xms6.5g -Xmx6.5g # 新生代的内存 -XX:NewSize=5.5g -XXMaxNewSize=5.5g # 对外内存 -XX: MaxDirectMemorySize=1g

应用级别的性能调优#

问题:#


? Netty 基于 Reactor 这种线程模型的, 进行非阻塞式编程, 一般我们在编写服务端的代码时, 都会在 往 服务端的 Channel pipeline 上添加大量的 入站出站处理器, 客户端的消息一般我们都是在 handler 中的?ChannelRead()?或者是?ChannelRead0()?中取出来, 再和后台的业务逻辑结合


客户端的消息,会从 Pipeline 这个双向链表中的 header 中开始往后传播, 一直到 tail, 这其实是个责任链


这时, 如果我们将耗时的操作放在这些处理器中, 毫无疑问, nettey 会被拖垮, 系统的并发量也提升不上去


解决方式一:#


新开辟一个线程池 , 将耗时的业务逻辑放到新开辟的业务去执行


Copy


public class MyThreadPoolHandler extends SimpleChannelInboundHandler<String> { private static ExecutorService threadPool = Executors.newFixedThreadPool(20); @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { threadPool.submit(() -> { Object result = searchFromMysql(msg); ctx.writeAndFlush(ctx); }); } public Object searchFromMysql(String msg) { Object obj = null; // todo obj = return obj; } }


解决方式二:#


netty 提供的一种原生的解决方式, netty 可以将我们的 handler 放到一个专门的线程池中去处理

评论

发布
暂无评论
netty的线程模型, 调优 及 献上写过注释的源码工程