网络性能上限瓶颈
C10K
什么是 C10K 问题?C 指的是 client,所谓的 C10K 就是系统如何支持 1 万并发的客户端请求
I/O 事件通知的方式
水平触发和边缘触发,用在套接字的文件描述符中
水平触发
只要文件描述符可以非阻塞地执行 I/O,就会触发通知。
应用程序可以随时检查文件描述符的状态,根据状态进行 I/O 操作
边缘触发
只有文件描述的状态发生改变时,也就是 I/O 请求到达的时候才触发通知。
此时程序需要尽可能多地执行 I/O,知道无法继续读写。
如果 I/O 没有执行完或者没有来得及处理,那么这次通知也就丢失了。
I/O 多路复用
非阻塞I/O + 水平触发
select 和 poll 需要从文件描述符列表中找到可以执行的 I/O,然后进行真正的网络 I/O 读写。
因为是非阻塞I/O,一个线程可以监控一组fd
问题:需要轮询所有的 fd,当请求多的时候就会慢
select
- 使用固定长度的位相量来表示 fd 的集合,所以有最大文件描述符的限制。
- 内部就是轮询实现的,加上通知之后程序的循环,就循环了两次
poll
- 没有了固定长度的数组,所以没有 fd 的限制
- 但还是轮询的,所以一样
两者每次调用需要将 fd 的集合在用户和内核空间传递,也有成本
非阻塞I/O + 边缘触发
epoll
- 使用红黑树管理 fd
- 在内核中管理 fd,不用切换
- 使用事件驱动机制,只关注有 I/O 事件的 fd
由于边缘触发只在 fd 可读写的时候才有通知,所以程序需要尽可能多的执行,并且需要处理更多的异常事件
异步I/O
异步 I/O 允许应用程序发起很多 I/O 操作,而不用等待完成。在 I/O 完成之后系统用事件通知告诉应用程序,此时应用程序才去查询 I/O 操作的结果
工作模型优化
主进程 + 多个 worker 子进程
主进程 bind() + listen() 子进程都通过 accept() 或 epoll_wait() 来处理相同的 socket。
惊群问题
当网络发生 I/O 时多个进程同时被唤醒,但是最后只有一个去处理
解决:nginx 通过全局锁解决,只有抢到锁的才会加入 epoll 中
监听相同端口的多进程
所有进程都监听相同端口,开启 SO_REUESPORT 由内核将请求负载均衡到进程中,内核确保了只有一个进程被唤醒,不会惊群
但 SO_REUSEPORT 需要 linux3.9 以上
继续优化?C100K? C10M?
借助硬件来完成,或者考虑 DPDK 和 XDP 来做,直接跳过内核协议栈,直接由用户态进程通过轮询来处理网络接收
IO 多路复用好文