Netty Reactor 模型之主从多线程模型

1. 前言

通过上节的分析,我们知道单 Reactor 多线程模型它的性能瓶颈在于单个 Reactor,本节主要讲解如何进行优化单个 Reactor 带来的性能瓶颈问题。

2. 单 Reactor 性能瓶颈

单 Reactor 主要存在的性能瓶颈如下:

  1. 压力问题: 客户端数量比较多的情况,单个 Reactor 负责监听和转发,那么 Reactor 压力非常的大;
  2. 单点故障问题: 如果 Reactor 发生故障,则即使后面的 Handler 和 Worker 正常工作,但是整个应用程序无法正常对外提供服务。

3. 如何进行优化

思考:如何解决单 Reactor 性能问题呢?

  1. 以 Tomcat 作为案例来进行分析:
    1.1 问题: 我们平时把项目打包成 war 部署到单个 Tomcat 来进行运行,在并发量很小的情况下是正常运行的,但是一旦并发量达到 1k 以上,单个 Tomcat 就会很吃力了,那怎么办呢?
    1.2 解决: 很简单,只需要在 Tomcat 前面加 Nginx 做负载转发,这样的话,多个 Tomcat 同时对外提供服务,不但整体的性能得到提高,即使其中一个 Tomcat 宕机,但是整个 Tomcat 集群还是能正常对外提供服务。
  2. 生活中饭馆的案例进行说明:
    还是以饭馆经营模型说明,方便大家更好的理解。
    2.1 一个饭馆只有一个老板,老板即兼职服务员和厨师的工作,整体效率很低,这就是单 Reactor 单线程模型;
    2.2 一个负责迎接客户、点菜、上菜的服务员(Reactor 线程),几个厨师负责炒菜(Worker 线程),厨师轻松了,但是服务员依然忙不过来,这就是单 Reactor 多线程模型;
    2.3 一个负责迎接在门口迎接小妹妹(好比:Reactor 主线程),几个专门负责点菜和上菜的服务员(好比:Reactor 从线程),几个负责超出厨师(Worker 线程),那么每个岗位都会很轻松,并且还能服务更多的客户进行就餐,这就是主从 Reactor 多线程模型。

其实,Reactor 模型也是类似道理,哪个环节性能存在瓶颈,那么将其功能再细分,并且增加执行数量(集群)即可。

4. 主从多线程模型

图片描述

架构图分析:

  1. 主要分为三个模块,分别为 Reactor 主线程、Reactor 子线程、Worker 线程池。其中 Reactor 主线程可以对应多个 Reactor 子线程,也就是说,一个 MainReactor 对应多个 SubReactor;
  2. Reactor 主线程的 MainReactor 对象通过 select 监听客户端连接事件,收到事件之后,通过 Acceptor 处理连接事件;
  3. 当 Acceptor 处理连接事件之后,MainReactor 将连接事件分配给 Reactor 子线程的 SubReactor 进行处理;
  4. SubReactor 将连接加入到连接队列进行监听,并且创建 Handler 处理对应的事件。一旦有新的事件(非连接)则分配给 Handler 进行处理;
  5. Handler 通过 read () 方法读取数据,并且分发给 Worker 线程池去做业务处理;
  6. Worker 线程池分配线程去处理业务,处理完成之后把结果返回给 Handler;
  7. Handler 收到 Worker 线程返回的结果之后,再通过 send () 方法返回给客户端。

方案的优点:

  1. 责任明确,单一功能拆分的更细,Reactor 主线程负责接收请求,不负责处理请求;Reactor 子线程负责处理请求。并发量很高的情况,可以减轻单个 Reactor 的压力,并且提高处理速度;
  2. Reactor 子线程只负责读取数据和响应数据,耗时的业务处理则丢给 Worker 线程池去处理。这种通过把完整任务层层分发下去,每个组件需要处理的内容就会变的很简单,处理起来效率自然会很高。

方案的缺点:

  1. 编程复杂度非常的高;
  2. 即使一个 Reactor 主线程对应多个 Reactor 子线程,Reactor 主线程还是会存在单节点故障问题,不过真实业务场景当中,如果考虑单节点故障问题的话,一般都是通过分布式集群(Netty 集群)的方式去解决,而不是靠单节点的线程模型去解决,这里大家了解一下即可。

总的来说,主从多线程模型是应用比较多的一种线程模型,包括 Nginx 主从 Reactor 多线程模型、Memcached 主从多线程模型、Netty 主从多线程模型等知名开源框架的。

5. 模型对比

Reactor 模型和传统的 IO 模型对比

传统 IO 模型 Reactor 模型
线程分配 为每个客户端都分配独立的线程,该线程负责全部的工作(包括:监听、读取、处理、响应) 统一的监听客户端请求,并且把功能细分,并且分配给不同的子线程去处理
堵塞点 在每个子线程的 read () 方法进行堵塞 只在 select () 堵塞,select () 是所有客户端共用的入口点
整体性能 并发量相对有限 可以处理高并发

Reactor 的整体优点如下:

  1. 性能好,Reactor 本身虽然是同步的,但是是非堵塞的,可以快速的响应;
  2. 扩展性好,可以根据 CPU 的核数来调整 Reactor 的实例个数,充分的利用 CPU 资源;
  3. 复用性好,它是一种思想,可以灵活的运用到不同的中间件、底层框架上。

三种 Reactor 线程模型对比

单 Reactor 单线程 单 Reactor 多线程 主从多线程
功能 一个线程负责所有业务 一个线程服务监听、事件处理、转发,多个线程负责逻辑处理 一个线程负责监听,多个线程负责事件处理、转发,多个线程负责逻辑处理
线程 一个线程 一个线程,一个线程组 一个线程,两个线程组
性能
高可用

6. 小结

通过这几个小节的讲解,相信大家对 Reactor 线程模型都已经有了一定的了解了,其实我们只需要了解这几种模型的架构思想即可。Reactor 它是一种思想,而并非是 Netty 所特有的,常见的中间件 Nginx、Redis 等底层通讯也都是基于 Reactor 思想去实现。只有把 Reactor 模型理解了,后期在阅读源码时才能更好的理解 Netty。