netty 的线程模型, 调优 及 献上写过注释的源码工程
Reactor 线程模型#
Reactor 线程模型, 顾名思义就像核反应堆一样, 是一种很劲爆的线程模型
最经典的两种图就像下面这样
上面两种图所描述的都是 Reactor 线程模型
Reactor 线程模型五大角色#
handle
handle 本质上表示是一种资源 , 在不同的操作系统上他们的名称又不一样, 比如在 windows 上成它为文件句柄 , 而在 linux 中称它为文件描述符, 其实我这样说, handle 的概念就显得比较抽象 不容易理解 , 具体一点说 handle 是啥呢 ? 比如客户端向服务端发送一个连接请求,这个 socket 请求对操作系统来说, 本质上就是 handle
**在 Reactor 线程模型中的 Handle 就是限制 netty 并发量的主要原因, 下面的调优主要也是为了突破这个限制
**
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 放到一个专门的线程池中去处理
评论