目录
文章目录
- 目录
- 主流的网络 I/O 框架
- C10K 问题
- 高性能网络 I/O 服务器模型
- 阻塞 IO + 多线程
- Reactor 模型(事件驱动处理模型)
- 单 Reactor,单线程模型
- 单 Reactor,多线程模型
- 主从 Reactor,多线程模型
- Proactor 模型
- 主流的中间件所采用的网络 I/O 模型
主流的网络 I/O 框架
- netty
- gnet
- libevent
- evio (golang)
- ACE (c++)
- boost::asio (c++)
- muduo (linux only)
C10K 问题
字母 C,表示:Concurrent(并发),C10K,即:10000 高并发。
虽然现在通过现成的网络 I/O 框架,例如:libevent、Netty 等,可以轻松地完成这个目标。但是在二十多年前,突破这个并发量还是非常困难的。
高并发网络 I/O 主要需要注意以下几点:
- Linux 的文件句柄数量:每个 Socket 连接的代表是一个文件描述符,如果 Linux 的文件句柄数量(默认:1024)不够用,那么新的连接将会被丢弃并产生错误。
- Linux 的系统内存:每个 Socket 连接都需要占用发送缓冲区(e.g. tcp_rmem 文件)和接收缓冲区(e.g. tcp_wmem 文件)地址空间。假设一个连接需要 128K 空间,1w 个连接就需要 1.2G 的 Userspace 地址空间。
- 网络带宽:假设 10 个连接,每个连接跑 10Mpps,包长为 64Byte,那么带宽为:10*(10Mpps1024**2) * (64Byte8) / (1024**3) = 50Gbps。
NOTE:BW = pps(包个数)* bit/p(包大小)
高性能网络 I/O 服务器模型
阻塞 IO + 多线程
由此,基于阻塞 IO,常见的 I/O 模型是:阻塞 IO + 多线程。例如:每个 TCP Connect 都使用一个单独的线程进来行处理。
但是,Application 能够创建的线程数量是有限的,因为线程本身就是一种开销。所以,就导致了 Application 可以处理的 Clients 数量受限制。面对百万连接的情况,是无法处理。
Reactor 模型(事件驱动处理模型)
所谓 Reactor(反应堆),即:在一个单独的线程中运行的、用于负责监听和分发事件的逻辑。比分:我们的手机可以设置为转接,将来自前任的电话转接给适当的联系人。
如下图,Application Server 通过统一的 ServiceHandler(Reactor)来进行 Request Dispatch(请求分发)。所以,这种软件架构也称为事件驱动处理模型(Event Loop)。
可见,Reactor(Event Loop)模型的本质是 “I/O 多路复用 + 线程池” 实现的网络 I/O 模型:
- 将 I/O 操作抽象为事件,为每个事件设置回调函数。
- 将非 I/O 操作解耦到其他 Worker 线程中,释放 Reactor Dispatch 的压力。
- Application 通过 Reactor Dispatch(实现了 I/O 多路复用)来统一监听事件,并在收到事件之后进行任务分发。
根据 Reactor 的数量和处理的资源大小通常又分为:
- 单 Reactor,单线程模型。
- 单 Reactor,多线程模型。
- 主从 Reactor,多线程模型。
单 Reactor,单线程模型
该模型,通常是只有一个 epoll 对象,所有的接收客户端连接、客户端读取、客户端写入操作都包含在一个线程内。
需要注意的是,在模式中,不仅 I/O 操作在该 Reactor 线程上,连非 I/O 的业务操作也在该线程上进行处理了,这可能会大大延迟 I/O 请求的响应。所以我们应该将非 I/O 的业务逻辑操作从 Reactor 线程上卸载,以此来加速 Reactor 线程对 I/O 请求的响应。
单 Reactor,多线程模型
该模型中,将读写的业务逻辑交给具体的线程池来实现,以此提升系统性能。
在线程池模式中,虽然非 I/O 操作交给了线程池来处理,但是所有的 I/O 操作依然由 Reactor 单线程执行,在高负载、高并发或大数据量的应用场景,依然较容易成为瓶颈。所以,对于 Reactor 的优化,又产生出下面的多线程模式。
主从 Reactor,多线程模型
在这种模型中,主要分为两个部分:mainReactor、subReactors。
- mainReactor:负责接收客户端的连接,然后将建立的客户端连接通过负载均衡的方式分发给 subReactors。
- subReactors:负责具体的每个连接的读写,对于非 IO 的操作,依然交给工作线程池去做,对逻辑进行解耦。
mainReactor 对应 Netty 中配置的 BossGroup 线程组,主要负责接受客户端连接的建立。一般只暴露一个服务端口,BossGroup 线程组一般一个线程工作即可 subReactor 对应 Netty 中配置的 WorkerGroup 线程组,BossGroup 线程组接受并建立完客户端的连接后,将网络 socket 转交给 WorkerGroup 线程组,然后在 WorkerGroup 线程组内选择一个线程,进行 I/O 的处理。WorkerGroup 线程组主要处理 I/O,一般设置 2*CPU 核数个线程。
Proactor 模型
proactor 主要是通过对异步 IO 的封装的一种模型,它需要底层操作系统的支持,目前只有 windows 的 IOCP 支持的比较好。