https://www.bilibili.com/video/BV1AzLS69EYr/?spm_id_from=333.1387.search.video_card.click&vd_source=a681ba71d1e6430511f4ae86200b353e
DeeparchWorks 2026-05-21 00:03:53
你有没有想过一个看HTTP服务器,一个做内存数据库,两个完全不搭界的项目,居然用的是同一套架构,links1台机器扛500000Q,PSREDIS单线程扛100000OS背后藏着同一个名字,reactor模式,今天我们就扒一扒,这个1995年从一篇论文走出来的模式,怎么成了所有高并发服务的灵魂,我们先抛一个反直觉的问题,如果给你一台16核机器,让你扛10万并发连接,你会怎么设计,99%的答案是,每个连接开一个线程,听起来天经地义对吧,可是eager cecif写mix时,给了一个完全不一样的答案,来看NX的架构,最上面那位是master,他手里拿着links c o n f不接客,只盯着下面四个worker干活,中间这四个worker,每个人脑袋上都有一个旋转的小圆环,那就是apple事件循环,下面那一长串小方块是源源不断涌进来的,HTTP连接,你注意看右下角那个技术活跃连接数9100万,但worker就四个,每个worker同时在处理2000多个连接,这就是reactor模式的精髓,线程数远远小于连接数,要理解为什么这妖牛,我们先得搞清楚reactor到底是什么,1995年,华盛顿大学的douglas smith教授发表论文,第一次把这个模式形式化为四个角色,最上层是reactor事件循环本体,下面是同步事件,多路分解器,在LINUX上就是apple,在下面是事件处理器接口,最底下是业务自己实现的具体处理器。
这个分层是所有reactor实现的统一蓝图,CHMT不仅写论文,还亲手做了一个C网络框架,叫ace ace,这是90年代最有影响力的网络中间件,后来的NXREDISNEI虽然没有直接抄S的代码,但都继承了他的灵魂,可以说没有思mate这篇论文,整个高并发服务领域要晚10年,reactor不是只有一种长相,常见有三个变种,最简单的单reactor,单线程从apple下划线,weight到业务回调全在一个线程里,这就是REDIS5X第二种是单reactor加worker池,IO一个线程业务多个线程,第三种是主从reactor,一个main reactor,专门accept新连接N个sob reactor,各跑独立APRIL处理读写net,就是这套HINKS走的是另一条路,等下你就明白,你可能在想,美联接一线城不挺好吗,编程简单,隔离干净,但在1万连接量级这条路被三堵墙堵死了,内存强先杀你,LINUX每个线程默认八兆站,1万线程直接吃掉80G内存,普通服务器一启动就own,退一步用更小的站调度墙又来了1万线程,在CPU进来回切,光是调度器本身就吃30%,Cpu,再退一步,用select select,每次都要把整个SD集合从用户太口到内核态,1万个SD就是10万次空轮巡,三堵墙叠在一起,C10K就成了一道死结,2002年LINUX2.5.44引入了apple,把select的三个痛点全部解决,apple的关键创新是把判断哪个FD就绪,从轮询挪到了硬中断回调网卡来包硬中断,触发软中断。
调用SK下划线,data下划线ready唤醒,apple把就绪的SD挂到一个ready list,APRIL下划线weight只是从这个list low已经准备好的,完全不轮询,从O嗯变成O就叙述来看一个画面,中间这位是接线员,周围16部电话,如果是select的工作方式,他要一步一步去拿起来听,有没有想有没有想,1万部电话他要轮1万次,EPOL不一样,电话灯亮才接,灯不亮根本不看,所以你看现在16部电话只有二步量,他只处理这二部,剩下14部对他来说不存在,这就是为什么apple能扛百万连接reactor模式,加上apple c10K就被一脚踢飞,一个线程加一个apple sd,100万个连接的内存才100兆零次跨线程切换,APRIL下划线,wait一次返回所有就绪事件,这就是HANS1台16核,打死APACHE几百个进程的根本原因,道理讲完了,下面进入硬核环节,打开INX源码,看真正的reactor长什么样,LINUX的进程模型很清晰,一个master负责管理N个worker,各自跑独立的apple事件循环,master只在启动时BINT端口创建listen sd,然后fork出N个worker,自己再也不处理任何请求,所有真正的活都在worker的事件循环里,LINUX的作者ego CEO f是俄罗斯工程师,当年在rambler门户被APACHE折磨的受不了,自己重写了一个他的设计哲学,就一句话,一个master加N个worker,每个worker是一个单线程事件循环,这套架构在20年后的今天。
仍然是教科书看master的主循环,一个永远在睡的死循环,核心就那条six spend,没事就睡,只在收到SCODECYUP这些信号时被唤醒,紫禁城死了,他去wait pit回收,收到reload,他就fork新worker加载新配置,master本身不处理任何HTTP请求,所有活在worker里,这是jinx worker的事件循环本体,每一轮做三件事,抢accept,锁掉apple下划线,weight分派事件,注意中间那个accept下划线,MTX它解决了一个经典的鲸群问题,N个worker都监听同一个listen sd新连接到达时,如果都被唤醒,只有一个能accept成功,其他白醒,所以HINKS让worker排队抢一把锁,同时只有一个worker能accept,这里就是reactor的灵魂时刻,apple下划线,weight拿到就绪,FD遍历每一个找到对应的connection,结构调它的handle回调,CHMT论文里的四个角色,在这十几行里全部到齐,apple下划线,weight是多路分解器,Data p t,二是事件到handler的映射,rap handler是event handle接口,整个函数就是reactor本体,HINKS看完了下一个主角登场,REDIS同样是reactor,但走的是完全相反的方向,REDIS的作者是意大利人SALVATORSANFILIPPO,江湖人称ANTARRASS,他写REDIS时只想着一件事,简单整个REDIS进程就一个线程跑事件循环,所有命令串行执行,他在REDIS6release note里写过一句话,单线程是REDIS简单可推理性的根基。
看REDIS5X的状态流,一个主线程,一个while循环,AAPIPO调APPOL下划线,weight便利就绪,事件读client,数据解析协议,执行命令,写回响应全部串行,整个REDIS没有任何线程同步代码,这种朴素到极致的设计,恰恰是REDIS性能可预测的根源,和wings是不是一模一样,AE main就是个死循环,每一轮调AE process events里面是APPOL下划线,weight加事件分派,读事件调fl p l c写事件调o file plo c,注意那个before sleep钩子,这是REDIS6多线程IO的入口,等下你就看到REDIS把apple包了一层抽象叫A这样,同样一份AC主循环代码,在LINUX上用AE下划线apple c,在Mac os上用AE下划线,Coo c,在SOLARIS上用AE下划线,FPC跨平台一份代码搞定,这个适配层设计是ANTIREST的工程美学之一,为什么redis is敢用单线程看这个画面,左边一位大厨从食材到锅之间稳稳的走,全程一个人,这就是REDIS5X右边四个厨子争抢一口锅,三个在阻塞等待效率反而更低,REDIS操作的是内存纳秒级,所得成本远远大于命令执行本身的成本,所以单线程在这个场景下反而是最优解,但是好景不长,到了2020年万兆网卡时代,REDIS单线程read和write这两个CISCCALL开始扛不住,网卡的吞吐量远远大于单线程CSCO的吞吐量,ANITRESS在REDIS6.0加了一个东西,看REDIS6的架构中间还是那个主线程跑reactor。
循环命令执行仍然单线程,但是下面多了一组IO线程,专门干read和write这两个,CISCO注意IO线程不碰任何数据结构,只做协议解析和系统调用,命令执行,严格在主线程里串行,这样dict hit list这些数据结构仍然零锁,这段代码是REDIS6多线程IO的精髓,主线程把代写客户端round robin,分发给N个IO线程,自己也认领一份不当甩手掌柜,然后用一个自旋等所有IO线程跑完,注意这里全程没有锁,因为每个client同时只属于一个线程,这种用任务分区代替锁的思路,是ANTIRESS单线程哲学的延续,现在你看到了两种完全不同的reactor,实现LINUX多进程多reactor,REDIS单进程单reactor,为什么殊途同归,又为什么分道扬镳,你看这个对比就清楚了,kinks是CPU密集型,SSL握手跟zip压缩需要利用多核,所以选多进程,每个worker跑独立reactor,REDIS是内存密集型数据在一个地址空间最方便,所以选单进程靠cluster分片扩展,两者都用reactor,但变种完全不同,其实没有谁对谁错,只有谁更适合场景,CPU密集就多,进程内存密集就单进程共享状态多,就用单进程避免锁,共享状态少就用多进程隔离故障,这是eager和ANTIRESS,各自针对自己的问题做出的最优解,来跟一个HTTP请求从网卡到wings的完整旅程,0.01ms,网卡硬中断,0.08ms,apple下划线,weight醒来通知worker1毫秒读到请求1.4ms。
响应已经写到socket buffer,整个链路大概一毫秒,reactor的事件分派只占其中几微秒,这就是为什么LINUX能在单机做到500000QPS,最后一个反直觉的问题,携程会取代reactor吗,答案是不会,因为携程本身就建立在reactor之上,左边是reactor的回调风格,每一步都要写一个handle状态基地域,右边是携程的同步风格,看起来在一个函数里顺序执行,但你扒开gold net polar tokyo的apple run time,源码底层仍然是reactor,携程只是把写回调自动化,给程序员一个同步代码,异步性能的错觉,reactor不会过时,它是物理规律,讲到这里你应该明白了,reactor不是某个具体的函数或库,而是一种用事件驱动代替现成驱动的世界观,在C10K量级上,物理规律只允许这一种解,我们来回顾一下今天的核心概念,reactor是架构模式,apple是引擎,两者组合解决了C10K问题,LINUX走多进程路线,REDIS走单进程路线,但都是reactor的变种,无论是1995年SHMATE的论文,还是2002年EGD的wings,还是2009年ANTARREST的REDIS,都在告诉我们,同一件事事件驱动式高并发的唯一答案,回到我们一开始的问题,为什么LINUX和REDIS用同样的架构不是抄袭,是物理规律,当连接数远大于核心数时,唯一能抗住的就是事件驱动,下一次你打开任何一个高性能网络服务的源码,先去找他的事件循环,你会发现。
99%的高性能服务都是这个套路,从1995年SHMATE那篇论文开始,reactor已经统治了我们这个时代,所有的网络服务,如果觉得有收获,欢迎一键三连,我们下期见。
--- 由 vCaptions 生成 ---