1. 前言
通过上节的分析,我们知道单Reactor多线程模型它的性能瓶颈在于单个Reactor,本节主要讲解如何优化单个Reactor带来的性能瓶颈问题。
2.单个Reactor性能瓶颈
单 Reactor 主要存在的性能瓶颈如下:
- 压力问题: 客户端数量比较多的情况,单个 Reactor 负责监听和转发,那么 Reactor 压力非常的大;
- 单点故障问题: 如果 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.主从多线程模型
架构图分析:
- 主要分为三个模块,分别为 Reactor 主线程、Reactor 子线程、Worker 线程池。其中 Reactor 主线程可以对应多个 Reactor 子线程,也就是说,一个 MainReactor 对应多个 SubReactor;
- Reactor 主线程的 MainReactor 对象通过 select 监听客户端连接事件,收到事件之后,通过 Acceptor 处理连接事件;
- 当 Acceptor 处理连接事件之后,MainReactor 将连接事件分配给 Reactor 子线程的 SubReactor 进行处理;
- SubReactor 将连接加入到连接队列进行监听,并且创建 Handler 处理对应的事件。一旦有新的事件(非连接)则分配给 Handler 进行处理;
- Handler 通过 read () 方法读取数据,并且分发给 Worker 线程池去做业务处理;
- Worker 线程池分配线程去处理业务,处理完成之后把结果返回给 Handler;
- Handler 收到 Worker 线程返回的结果之后,再通过 send () 方法返回给客户端。
方案的优点:
- 责任明确,单一功能拆分的更细,Reactor 主线程负责接收请求,不负责处理请求;Reactor 子线程负责处理请求。并发量很高的情况,可以减轻单个 Reactor 的压力,并且提高处理速度;
- Reactor 子线程只负责读取数据和响应数据,耗时的业务处理则丢给 Worker 线程池去处理。这种通过把完整任务层层分发下去,每个组件需要处理的内容就会变的很简单,处理起来效率自然会很高。
方案的缺点:
- 编程复杂度非常的高;
- 即使一个 Reactor 主线程对应多个 Reactor 子线程,Reactor 主线程还是会存在单节点故障问题,不过真实业务场景当中,如果考虑单节点故障问题的话,一般都是通过分布式集群(Netty 集群)的方式去解决,而不是靠单节点的线程模型去解决,这里大家了解一下即可。
总的来说,主从多线程模型是应用比较多的一种线程模型,包括 Nginx 主从 Reactor 多线程模型、Memcached 主从多线程模型、Netty 主从多线程模型等知名开源框架的。
5.模型对比
Reactor 模型和传统的 IO 模型对比
传统 IO 模型 | Reactor 模型 | |
---|---|---|
线程分配 | 为每个客户端都分配独立的线程,该线程负责全部的工作(包括:监听、读取、处理、响应) | 统一的监听客户端请求,并且把功能细分,并且分配给不同的子线程去处理 |
堵塞点 | 在每个子线程的 read () 方法进行堵塞 | 只在 select () 堵塞,select () 是所有客户端共用的入口点 |
整体性能 | 并发量相对有限 | 可以处理高并发 |
Reactor 的整体优点如下:
- 性能好,Reactor 本身虽然是同步的,但是是非堵塞的,可以快速的响应;
- 扩展性好,可以根据 CPU 的核数来调整 Reactor 的实例个数,充分的利用 CPU 资源;
- 复用性好,它是一种思想,可以灵活的运用到不同的中间件、底层框架上。
三种 Reactor 线程模型对比
单 Reactor 单线程 | 单 Reactor 多线程 | 主从多线程 | |
功能 | 一个线程负责所有业务 | 一个线程服务监听、事件处理、转发,多个线程负责逻辑处理 | 一个线程负责监听,多个线程负责事件处理、转发,多个线程负责逻辑处理 |
线程 | 一个线程 | 一个线程,一个线程组 | 一个线程,两个线程组 |
性能 | 低 | 中 | 高 |
高可用 | 否 | 否 | 是 |
6.小结
通过这几个小节的讲解,相信大家对 Reactor 线程模型都已经有了一定的了解了,其实我们只需要了解这几种模型的架构思想即可。Reactor 它是一种思想,而并非是 Netty 所特有的,常见的中间件 Nginx、Redis 等底层通讯也都是基于 Reactor 思想去实现。只有把 Reactor 模型理解了,后期在阅读源码时才能更好的理解 Netty。