从 0 开始学架构
李运华
网名“华仔”,前阿里资深技术专家(P9)
152573 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 66 讲
结束语 (1讲)
结课测试 (1讲)
从 0 开始学架构
15
15
1.0x
00:00/00:00
登录|注册

19 | 单服务器高性能模式:Reactor与Proactor

父子进程之间简单通信
子进程独立处理连接的各种事件
子进程的subReactor监听连接事件
父进程mainReactor负责接收新连接
Reactor可能成为性能瓶颈
多线程数据共享和访问复杂
子线程的Processor处理业务
主线程中Reactor监听连接事件
适用于业务处理快速的场景
处理资源池负责处理事件
Reactor负责监听和分配事件
Proactor模式实现真正的异步I/O
Reactor模式常用于高并发网络编程
Reactor适用于Linux系统,Proactor适用于Windows系统
Reactor和Proactor是单服务器高性能模式
Proactor根据事件类型回调Handler
Asynchronous Operation Processor处理I/O操作
Proactor Initiator创建Proactor和Handler
Proactor模型实现真正的异步I/O
异步I/O能够充分利用DMA特性
异步网络模型
多Reactor多进程/线程
单Reactor多线程
单Reactor单进程/线程
非阻塞的I/O多路复用技术解决进程阻塞问题
引入资源池解决进程创建销毁的浪费问题
PPC和TPC模式无法支撑高并发场景
总结
Proactor
Reactor
单服务器高性能模式:Reactor与Proactor

该思维导图由 AI 生成,仅供参考

专栏上一期我介绍了单服务器高性能的 PPC 和 TPC 模式,它们的优点是实现简单,缺点是都无法支撑高并发的场景,尤其是互联网发展到现在,各种海量用户业务的出现,PPC 和 TPC 完全无能为力。今天我将介绍可以应对高并发场景的单服务器高性能架构模式:Reactor 和 Proactor。

Reactor

PPC 模式最主要的问题就是每个连接都要创建进程(为了描述简洁,这里只以 PPC 和进程为例,实际上换成 TPC 和线程,原理是一样的),连接结束后进程就销毁了,这样做其实是很大的浪费。为了解决这个问题,一个自然而然的想法就是资源复用,即不再单独为每个连接创建进程,而是创建一个进程池,将连接分配给进程,一个进程可以处理多个连接的业务。
引入资源池的处理方式后,会引出一个新的问题:进程如何才能高效地处理多个连接的业务?当一个连接一个进程时,进程可以采用“read -> 业务处理 -> write”的处理流程,如果当前连接没有数据可以读,则进程就阻塞在 read 操作上。这种阻塞的方式在一个连接一个进程的场景下没有问题,但如果一个进程处理多个连接,进程阻塞在某个连接的 read 操作上,此时即使其他连接有数据可读,进程也无法去处理,很显然这样是无法做到高性能的。
解决这个问题的最简单的方式是将 read 操作改为非阻塞,然后进程不断地轮询多个连接。这种方式能够解决阻塞的问题,但解决的方式并不优雅。首先,轮询是要消耗 CPU 的;其次,如果一个进程处理几千上万的连接,则轮询的效率是很低的。
为了能够更好地解决上述问题,很容易可以想到,只有当连接上有数据的时候进程才去处理,这就是 I/O 多路复用技术的来源。
I/O 多路复用技术归纳起来有两个关键实现点:
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了在互联网高并发场景下,单服务器的高性能架构模式,主要介绍了Reactor和Proactor两种适用于高并发场景的模式。Reactor模式通过事件驱动的方式实现高并发处理,能够有效地支撑大量并发请求,提高服务器性能。而Proactor模式则更加注重异步处理,通过预先发起I/O操作来提高系统的并发能力,适用于需要处理大量I/O操作的场景。 文章详细介绍了Reactor模式的实现原理和优势,以及在不同业务场景下的具体应用方案。其中包括单Reactor单进程/线程、单Reactor多线程和多Reactor多进程/线程等不同的实现方案,以及它们各自的优缺点和适用场景。通过深入了解Reactor和Proactor模式,读者可以更好地应对互联网发展带来的挑战,提升服务器性能,满足用户需求。 总的来说,本文通过对Reactor和Proactor模式的详细介绍,为读者提供了在高并发场景下选择合适的单服务器高性能架构模式的参考,使读者能够快速了解并掌握这两种模式的特点和优势,为构建高性能的单服务器架构提供了有益的指导。文章中还对Reactor模式的具体实现方案进行了深入分析,为读者提供了丰富的实践经验和技术指导。通过本文的阅读,读者可以更好地理解和应用Reactor和Proactor模式,从而提升系统的性能和稳定性,满足不同业务场景下的需求。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《从 0 开始学架构》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(102)

  • 最新
  • 精选
  • IO操作分两个阶段 1、等待数据准备好(读到内核缓存) 2、将数据从内核读到用户空间(进程空间) 一般来说1花费的时间远远大于2。 1上阻塞2上也阻塞的是同步阻塞IO 1上非阻塞2阻塞的是同步非阻塞IO,这讲说的Reactor就是这种模型 1上非阻塞2上非阻塞是异步非阻塞IO,这讲说的Proactor模型就是这种模型

    作者回复: 解释很清楚👍👍

    2018-06-10
    11
    516
  • Reactor与Proactor能不能这样打个比方: 1、假如我们去饭店点餐,饭店人很多,如果我们付了钱后站在收银台等着饭端上来我们才离开,这就成了同步阻塞了。 2、如果我们付了钱后给你一个号就可以离开,饭好了老板会叫号,你过来取。这就是Reactor模型。 3、如果我们付了钱后给我一个号就可以坐到坐位上该干啥干啥,饭好了老板会把饭端上来送给你。这就是Proactor模型了。

    作者回复: 太形象了👍👍👍

    2018-06-11
    18
    486
  • 正是那朵玫瑰
    感谢华仔,我也再实验了下netty4,其实handler的独立的线程池里面执行其实也没有问题,netty已经帮我们处理好了,当我们处理完业务,write数据的时候,会先放到一个队列里面,真正出站还是由io线程统一调度,这样就避免了netty3的问题!

    作者回复: 非常感谢,我明白了你说的情况,我再次验证了一下,写了一个独立线程处理业务的,确实如你所说,netty4两者都支持,并且做了线程安全处理,最终发送都是在io线程里面。 如果我们用这种模式,可以自己控制业务线程,因为netty4已经帮我们封装了复杂度,看来我孤陋寡闻了😂 不过我建议还是别无条件用这种模式,我们之前遇到的情况就是短时间内io确实很快,并发高,但如果业务处理慢,会积压请求数据,如果客户端请求是同步的,单个请求全流程时间不会减少;如果客户端请求是异步的,如果积压的时候宕机会丢较多数据。 其实这种情况我理解单纯加大线程数就够了,例如5个io线程加20个业务线程能达到最优性能的话,我理解25个融合线程性能也差不多。 我们之前有一个案例,http服务器业务处理线程配置了512个,后来发现其实配置128是最好的(48核),所以说并不是线程分开或者线程数量多性能就一定高。 再次感谢你的认真钻研,我也学到了一个技术细节👍

    2018-06-15
    98
  • 正是那朵玫瑰
    根据华仔之前对前浪微博消息中间件的分析,TPS定位在1380,QPS定位在13800,消息要高可靠(不能丢失消息),定位在常量连接海量请求的系统吧。基于此来分析下吧。 1、单Reactor单进程/线程 redis采用这种模式,原因是redis是基于内存的数据库,在处理业务会非常快,所以不会对IO读写进行过长时间的阻塞,但是如果redis开启同步持久化后,业务处理会变慢,阻塞了IO线程,也就无法处理更多的连接了,而我们的消息中间件需要消息的高可靠,必定要同步持久化,如果异步的话,就看异步持久化的时间间隔了,假设500ms持久化一次,那就有可能会丢失500ms的消息。当然华仔分析的无法利用多核cpu的特性也是一大缺点;虽然我们要求的TPS不算很高,但是QPS很高了,所以我觉得这种模式不合适 2、单Reactor多进程/线程 这种模式我觉得也不是和合适,虽然真正的业务处理在独立的线程了,IO线程并没有被阻塞,可以处理更多的连接和读写事件。我们的中间件可能不会面对海量的连接数,但是会面对大量的读请求,瓶颈是在处理读操作上,跟单Reactor单进程/线程差别不大;我倒觉得前一讲说的TPC prethread 模式是合适的,有独立的线程负责read-业务处理-send。 3、多Reactor多进程/线程 这种模式是最合适的了,不过华仔在讲解是read→业务处理→send,业务处理还是在IO线程上,如果业务处理的慢,还是会阻塞IO线程的,我觉得最好是业务处理放到独立的线程池里面去,这就变成了mainReactor负责监听连接,subReactor 负责IO读写,后面的业务线程池负责真正的业务处理,这样就既可以面对海量的连接,海量的请求也可以支撑。 不知理解的是否正确?

    作者回复: 1. 分析正确,redis不太适合有的key的value特别大,这种情况会导致整个redis变慢,这种场景mc更好 2. prethread确实可以,mysql就是这种模式 3. 多reactor多线程再拆分业务线程,性能没有提升,复杂度提升不少,我还没见过这种方式。

    2018-06-09
    5
    63
  • 空档滑行
    消息队列系统属于中间件系统,连接数相对固定,长链接为主,所以把accept分离出来的意义是不大的。消息中间件要保证数据持久性,所以入库操作应该是耗时最大的操作。综合起来我觉得单reactor,多线程/进程的方式比较合适。

    作者回复: 分析正确

    2018-06-10
    2
    45
  • LinMoo
    请教两个问题 谢谢 之前学习NIO和AIO的时候是这么描述的:进程请求IO(无论是硬盘还是网络IO),先让内核读取数据到内核缓存,然后从内核缓存读取到进程。这里面就有2个IO等待时间,第一个是读取到内核缓存,第二个是读取到进程。前者花费的时间远远大于后者。在第一个时间中进程不做等待就是NIO,即非阻塞。第二个时间中进程也不需要等待就是AIO,即异步。 第一个问题:文章中说Reactor 是非阻塞同步网络模型,因为真正的 read 和 send 操作都需要用户进程同步操作。这里的read和send指的是我上面说的第二个时间吗? 第二个问题:因为我理解你的“来了事件我来处理,处理完了我通知你”。这里的我来处理就是包括第一和第二个时间吗? 感觉我之前被误解了,是我哪个地方理解不对吗?麻烦解答一下。

    作者回复: 你两个问题的理解都正确

    2018-06-09
    25
  • 正是那朵玫瑰
    感谢华仔的解答,我看到在针对多reactor多线程模型,也有同学留言有疑问,我想请教下华仔,多reactor多线程模型中IO线程与业务处理在同一线程中,如果业务处理很耗时,定会阻塞IO线程,所以留言同学“衣申人”也说要不要将IO线程跟业务处理分开,华仔的答案是性能没有提升,复杂度提升很多,我还没见过这种处理方式,华仔对netty应该是很熟悉的,我的疑问是:在netty中boss线程池就是mainReactor,work线程池就是subReactor,正常在ChannelPipeline中添加ChannelHandler是在work线程即IO线程中串行执行,但是如果pipeline.addLast(group, "handler", new MyBusinessLogicHandler());这样的话,业务hangdle就会在group线程池里面执行了,这样不就是多reactor多线程模型中把IO线程和业务处理线程分开么?而且我在很多著名开源项目里面看到使用netty都是这样处理的,比如阿里的开源消息中间件rocketmq使用netty也是如此。华仔说没有见过这种处理方式,能否解答下?不知道是不是我理解错了

    作者回复: 非常感谢你的认真研究和提问,我对netty的原理有一些研究,也用过netty3,也看过一些源码,但也还达不到非常熟悉的地步,所以不管是网上的资料,还是我说的内容,都不要无条件相信,自己都需要思考,这点你做的很好👍 回到问题本身,由于netty4线程模型和netty3相比做了改进,我拿netty4.1源码中的telnet样例,在handler和NioEventloop的processSelectedKey函数打了输出线程id的日志,从日志结果看,StringEncoder, StringDecoder, TelnetServerHandler都在NioEventLoop的线程里面处理的。 如果handler在独立的线程中运行,返回结果处理会比较麻烦,如果返回结果在业务线程中处理,会出现netty3存在的问题,channel需要做多线程同步,各种状态处理很麻烦;如果返回结果还是在io线程处理,那业务线程如何将结果发送给io线程也涉及线程间同步,所以最终其实还不如在一个线程里面处理。

    2018-06-14
    4
    17
  • 沙亮亮
    根据unix网络编程上说的,select和poll都是轮询方式,epoll是注册方式。为什么您说select也不是轮询方式

    作者回复: 两个轮询不是一个意思,select和poll是收到通知后轮询socket列表看看哪个socket可以读,普通的socket轮询是指重复调用read操作

    2018-07-16
    12
  • 努力成为架构师的萌新
    萌新,没有什么实践经验,理解和总结的可能不到位,也有些疑问希望得到解答 总结: 少连接,多请求 - PPC/TPC 多连接,多请求 - 单Rector 单线程 (无法充分利用内核,需要业务处理迅速) - 单Rector 多线程 (复杂度较高,应对瞬间高并发能力较差) - 多Rector 多线程 (复杂度比 单Rector多线程 低,强化应对高并发的能力) 疑问: 1. 多Rector多线程 相比于其他Rector模式的缺点是什么, 既可以充分利用内核,复杂度不错,也有一定应对高并发的能力,岂不是万金油的选择? 2. 多Rector多线程/进程 的模式和PPC/TPC很像,都是在请求连接的时候开始新线程/进程 进行处理,这两者之间有什么区别? 后浪微博的场景会有多连接,多请求(访问量),并且可能存在高并发的场合, 所以可以采用多Rector多线程(分析错的话希望指点)

    作者回复: 你这个萌新有点强啊,总结的很到位 👍🏻 疑问解答: 1. 多Reactor多线程目前来说几乎是完美的方案,没有明显的缺点,唯一的缺点是实现比较复杂一些,但是目前都有开源方案,Java的Netty,C/C++的libevent 2. 区别就是当一个线程没事可干的时候是如何阻塞的,多Reactor多线程里面的线程,一个线程可以处理几十上百个连接,没事做的时候就阻塞在select上,而PPC/TPC一个线程只能处理一个连接,没事做的时候就阻塞在read上。

    2021-06-25
    11
  • ban
    这个Reactor Proactor好抽象,不太理解

    作者回复: 对照Doug Lee讲异步io的PPT,将代码从头到尾亲自敲一遍,就比较容易理解了

    2018-12-02
    7
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部