这篇文章上次修改于 765 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

1 C10K

系统中支持 1 万并发请求。

  • 一个线程中处理多个请求。可采用非阻塞或异步 I/O 方式。
  • 用更少的线程服务请求。

1.1 I/O 模型优化

两种 I/O 事件通知方式:

  • 水平触发,只要文件描述符可以非阻塞地触发通知,就可以触发 I/O。即应用程序可以随时地检查文件描述符状态,然后根据状态进行 I/O 操作。
  • 边缘触发,只有在文件描述符状态发生改变时(即 I/O 请求到达),才发送一次通知。这时需要应用程序尽可能多地执行 I/O,直到无法读写。

I/O 多路复用方法:

  • 第一种,采用非阻塞 I/O 和水平触发通知,如 select 和 poll。

    • 优点:简单,对应用程序友好。
    • 缺点:应用程序每次调用 select 或 poll 时,需要对文件描述符轮询,耗时较多。select 还有固定文件描述符数量限制。此外,每次调用 select 或 poll 时,还需要将文件描述符从用户空间传入内核,内核修改后再传回用户空间,存在切换开销。
  • 第二种,使用非阻塞 I/O 和边缘触发通知,如 epoll。

    • 使用红黑树在内核中管理文件描述符,不需要传入、传出文件描述符。
    • 使用事件驱动机制,只需要关注有 I/O 事件发送的文件描述符,无需轮询。
  • 第三种,使用异步 I/O。应用可同时发起多个 I/O 操作,而不需要等待 I/O 结束,I/O 完成后,系统会用事件通知的方式告知应用。

1.2 工作模型优化

第一种,主进程 + 多个子进程:

  • 主进程执行 bind() + listen() 后,创建多个子进程。
  • 在每个子进程中采用 accept() + epoll_wait() 处理相同的套接字。

对于惊群问题:

  • accept() 惊群问题已在 Linux 2.6 中解决。
  • epoll 惊群问题已在 Linux 4.5 中通过 EPOLLEXCLUSIVE 解决。

为避免进程频繁创建,这些进程在没任务时休眠,有任务时唤醒。

为降低切换开销,可用线程代替进程。甚至将 epoll_wait() 放在主线程中,确保每次事件只唤醒主线程。

第二种,监听到相同端口的多进程模型。所有的进程都监听相同端口,并开启 SO_REUSEPORT 选项,由内核将请求负载均衡到监听进程中。

2 C1000K

还需要从应用程序到 Linux 内核、再到 CPU、内存和网络等各个层次的深度优化,特别是需要借助硬件,来卸载那些原来通过软件处理的大量功能。

3 C10M

要解决这个问题,最重要就是跳过内核协议栈的冗长路径,把网络包直接送到要处理的应用程序那里去。这里有两种常见的机制,DPDK 和 XDP。

  • XDP:在内核协议栈之前,先处理网络包。
  • DPDK:跳过网络协议栈,在用户空间采用轮询方式。

参考

倪朋飞. Linux 性能优化实战.