【Linux 性能优化系列】Linux 性能优化 -- 网络性能篇 (C10K、C1000K、C10M 问题总结)

【Linux 性能优化系列】Linux 性能优化 -- 网络性能篇 (C10K、C1000K、C10M 问题总结)

【1】C10K 问题以及优化方法简介

C10K 表示单机同时处理 1 万个请求 (并发连接 1 万) 的问题;

【1.1】主要待解决的问题

  • 问题一,怎样在一个线程内处理多个请求,即要在一个线程内响应多个网络 I/O;
  • 问题二,怎么更节省资源地处理客户请求,即要用更少的线程来服务这些请求;

【1.2】事件 IO 通知的方式

  • 两种 I/O 事件通知的方式,水平触发和边缘触发;
    • 水平触发,只要文件描述符可以非阻塞地执行 I/O,就会触发通知,即应用程序可以随时检查文件描述符的状态,然后再根据状态,进行 I/O 操作;
    • 边缘触发,只有在文件描述符的状态发生改变时,才发送一次通知,此时,应用程序需要尽可能多地执行 I/O,直到无法继续读写,才可以停止;若 I/O 没执行完,或者因为某种原因没来得及处理,那么这次通知也就丢失了;

【1.3】优化方案

【1.3.1】I/O 模型优化

使用非阻塞 I/O 和水平触发通知 (比如使用 select 或者 poll)

  • 根据水平触发的特性,select 和 poll 可以从文件描述符列表中,找出可以执行 I/O 操作的文件描述符,然后进行真正的网络 I/O 操作,由于 I/O 是非阻塞的,一个线程中就可以同时监控一批套接字的文件描述符,从而实现单线程处理多请求的目的;
  • 缺陷
    • 应用程序使用 select 和 poll 时,需要对这些文件描述符列表进行轮询,在请求数多的时候就会比较耗时;
    • select 使用固定长度的位向量,表示文件描述符的集合,存在最大描述符数量的限制;并且,在 select 内部,检查套接字状态使用轮询的方法,处理耗时跟描述符数量是 O(N) 的关系;
    • 应用程序每次调用 select 和 poll 时,需要把文件描述符的集合,从用户空间传入内核空间,由内核修改后,再传出到用户空间中,内核空间与用户空间切换增加了处理成本;

使用非阻塞 I/O 和边缘触发通知 (比如 epoll)

  • epoll 使用红黑树,在内核中管理文件描述符的集合,从而就不需要应用程序在每次操作时都传入、传出该集合;
  • epoll 使用事件驱动的机制,只关注有 I/O 事件发生的文件描述符,不需要轮询扫描整个集合;
  • 缺陷
    • 边缘触发只在文件描述符可读或可写事件发生时才通知,从而应用程序就需要尽可能多地执行 I/O 并要处理更多的异常事件;

使用异步 I/O (Asynchronous I/O,简称为 AIO)

  • 异步 I/O 允许应用程序同时发起很多 I/O 操作,而不用等待这些操作完成,而在 I/O 完成后,系统会用事件通知 (比如信号或者回调函数)的方式,通知应用程序;应用程序接到通知才会去查询 I/O 操作的结果;
  • 缺陷
    • 目前该方式不太完善,使用比较困难

【1.3.2】工作模型优化

1. 主进程 + 多个 worker 子进程

  • 主进程执行 bind() + listen() 后,创建多个子进程;
  • 在每个子进程中,通过 accept() 或 epoll_wait(),来处理相同的套接字;

【Linux 性能优化系列】Linux 性能优化 -- 网络性能篇 (C10K、C1000K、C10M 问题总结)_第1张图片

惊群问题

  • accept() 和 epoll_wait() 调用的惊群问题,即当网络 I/O 事件发生时,多个进程被同时唤醒,但实际上只有一个进程来响应这个事件,其他被唤醒的进程都会重新休眠;
  • 解决
    • accept() 的惊群问题,已经在 Linux 2.6 中解决;
    • epoll 的问题,在 Linux 4.5 通过 EPOLLEXCLUSIVE 解决;
    • Nginx 在每个 worker 进程中,都增加一个了全局锁(accept_mutex),这些 worker 进程需要首先竞争到锁,只有竞争到锁的进程,才会加入到 epoll 中,这样就确保只有一个 worker 子进程被唤醒;

2. 监听到相同端口的多进程模型

  • 该方式下,所有的进程都监听相同的端口并且开启 SO_REUSEPORT 选项,由内核负责将请求负载均衡到这些监听进程中

【Linux 性能优化系列】Linux 性能优化 -- 网络性能篇 (C10K、C1000K、C10M 问题总结)_第2张图片

【2】C1000K 问题与优化

物理资源

  • 100 万个请求需要大量的系统资源;
    • 内存,假设每个请求需要 16KB 内存,则总共就需要大约 15 GB 内存;
    • 带宽,假设只有 20% 活跃连接,即使每个连接只需要 1KB/s 的吞吐量,总共需要 1.6 Gb/s 的吞吐量;需要配置万兆网卡,或者基于多网卡 Bonding 承载更大的吞吐量;

软件资源

  • 大量的连接会占用大量的软件资源;
    • 比如文件描述符的数量、连接状态的跟踪(CONNTRACK)、网络协议栈的缓存大小(比如套接字读写缓存、TCP 读写缓存)等等;

大量请求带来的中断处理

  • 需要多队列网卡、中断负载均衡、CPU 绑定、RPS/RFS (软中断负载均衡到多个 CPU 核上),以及将网络包的处理卸载 (Offload) 到网络设备(如 TSO/GSO、LRO/GRO、VXLAN OFFLOAD) 等各种硬件和软件的优化;

C1000K 的解决方法,本质上是构建在 epoll 的非阻塞 I/O 模型上,除了 I/O 模型之外,还需要从应用程序到 Linux 内核、再到 CPU、内存和网络等各个层次的深度优化,特别是需要借助硬件,来卸载那些原来通过软件处理的大量功能;

【3】C10M 问题与优化

核心思想是跳过内核协议栈的冗长路径,把网络包直接送到要处理的应用程序中处理,这里有两种常见的机制,DPDK 和 XDP;

DPDK

  • DPDK 是用户态网络的标准,跳过内核协议栈,直接由用户态进程通过轮询的方式处理网络接收;

【Linux 性能优化系列】Linux 性能优化 -- 网络性能篇 (C10K、C1000K、C10M 问题总结)_第3张图片


在 PPS 非常高的场景中,查询时间比实际工作时间少了很多,绝大部分时间都在处理网络包;而跳过内核协议栈后,就省去了繁杂的硬中断、软中断再到 Linux 网络协议栈逐层处理的过程,应用程序可以针对应用的实际场景,有针对性地优化网络包的处理逻辑,而不需要关注所有的细节;此外,DPDK 通过大页、CPU 绑定、内存对齐、流水线并发等多种机制,优化网络包的处理效率;

参考致谢

本博客为博主的学习实践总结,并参考了众多博主的博文,在此表示感谢,博主若有不足之处,请批评指正。

【1】35 | 基础篇:C10K 和 C1000K 回顾

你可能感兴趣的:(Linux,系列,--,性能优化)