为什么基于事件驱动的服务器能实现高并发?
这种并发实际说的是并发的不活跃的长连接,并不是并发请求
典型的后端服务,在逻辑上可以划分为两层,
-
跟业务无关的通信层,负责socket连接的创建和管理,负责bind/listen/accept/send/recv…
-
通信层上面是业务逻辑层,负责被动响应请求,或主动推送业务消息
通信层特点:
- 都是IO行为,几乎不大消耗CPU
- 连接数很多,可能同时有10K甚至100K个TCP连接
- 通信协议就那么几种,decode/encode简单
- 外部网络是慢速IO,收发一点数据可能要1秒甚至更久
业务逻辑层特点:
- 少量CPU消耗,大部分时间在等待数据库或者其它网络服务返回
- 业务逻辑五花八门,逻辑中往往需要调用别的网络服务,如db
- 并发请求数,往往小于连接数,10K个连接,可能每秒只有100个请求
- 单个业务请求通常很快,毫秒级别,几十毫秒算慢的了
如果完全采用传统的多线程模型,1个tcp连接对应1个线程,10K个连接需要10K个线程,典型的内存消耗是10G。但是业务逻辑层并发请求往往要小1到2个数量级,每个请求往往只需要100ms以内,所以业务逻辑层需要的线程数,比通信层小2-3个数量级,不需要10K个线程,只要100个甚至10个就够了。
这种2-3个数量级差距的不匹配,促使了第一代事件驱动的流行,常见的模型,纷纷把通信层剥离出来,用事件驱动的形式取代多线程,但是在业务逻辑层仍然采用多线程模型。几个著名的例子:
- nginx负责通信层,PHP-FPM负责业务逻辑层
- nginx负责通信层,uwsgi负责Python的业务逻辑层
- tomcat nio负责通信层,业务逻辑层扔到线程池里处理
这些模型都很成功,用几百个甚至区区几十个线程/进程,满足了几万甚至几十万的并发连接。在整个199X年到2010年,这个模型都相当的适用,之后移动互联网兴起,随之也出现越来越多的网络API服务,我们的业务逻辑层,不但要跟内网的网络服务通信,还要跟外网的服务通信,在业务逻辑层也产生了大量的外网网络IO,导致一个请求不能在100ms内完成,可能增加到了500ms甚至几秒钟,其中大部分时间是在等待网络,白白的占用了一个线程等IO。
如果业务逻辑层也要消耗很多时间等待网络IO,那么它跟通信层的2-3个数量级的线程数量需求这个特性就被打破了,在极端的业务下,他们甚至重新回来1:1这个比例,举个例子,APP的广告服务,一个请求里可能包含3个广告位,每个广告位从20家广告供应商那里获取广告,再选价格最高的返回给APP展示。这里每一个http请求,需要对外发起60个http请求,按照一个100ms算,需要耗时6秒,如果每秒有1000个广告请求,就需要6000个线程才能不积压至服务崩溃。
所以第二代事件驱动模型应运而生,把业务逻辑也变成事件驱动,彻底消除浪费线程等待IO这个现象。事件驱动有两件常见的外衣,一件是异步回调,另一件是coroutine,近几年有了很多应用:
- Go的goroutine
- Python 3的coroutine
- Kotlin的coroutine
- nodejs的异步回调
- swoole 1的异步回调和swoole 2的coroutine
- erlang/elixir的process也算是coroutine
- VertX的异步回调…
第二代普遍出现的比较晚,而且只有大厂有这个流量需要用到,所以远没有第一代那么普及,coroutine和csp等概念都是几十年前提出的,流行和普及的晚,因此只能算是老年新生事物。
该文出自于知乎。https://www.zhihu.com/question/64727674