linux性能优化-网络

网络性能指标
  • 带宽:链路最大传输速率 b/s(比特每秒)
  • 吞吐量:单位时间成功传输的数据量,单位b/s
  • 延时:建立连接需要的时间或一个数据包往返时间RTT
  • PPS:以网络包为单位的传输速率,PPS用来评估网络的转发能力
  • 网络可用性 并发连接数 丢包率 重传率也是常用性能指标
网路配置

可以使用ifconfig或ip查看网络的配置

套接字信息

ifconfig和ip只显示了网络接口收发数据包的统计信息,网络协议栈中的统计信息,我们也必须关注。你可以用 netstat或者ss,来查看套接字、网络栈、网络接口以及路由表的信息。

可以使用ifconfig、netstat、ss、sar、ping 等工具查看网络性能。

C10K问题

99年提出,如何在32位 2GB内存 千兆网卡支持每秒1万并发。资源上来讲,只要每个请求处理占用不到200KB内存和100Kbit网络带宽即可,物理资源足够,然后就是软件问题,特别是网络I/O模型问题。

C10K之前,linux中网络处理都用同步阻塞的方式,即每个请求都分配一个进程或者线程,但是有10000个进程或者线程的调度、上下文切换乃至占用的内存都会成为瓶颈。为了支持1w并发,需要解决:

  1. 怎样在一个线程内处理多个请求,即一个线程内响应多个网络I/O;
  2. 更节省资源的处理客户请求,是否可以使用更少的线程

select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的

I/O模型优化
  • 水平触发:只要文件描述符可以非阻塞地执行 I/O ,就会触发通知。也就是说,应用程序可以随时检查文件描述符的状态,然后再根据状态,进行 I/O 操作
  • 边缘触发:只有在文件描述符的状态发生改变(也就是 I/O 请求达到)时,才发送一次 通知。这时候,应用程序需要尽可能多地执行 I/O,直到无法继续读写,才可以停止。 如果 I/O 没执行完,或者因为某种原因没来得及处理,那么这次通知也就丢失了
  1. 使用非阻塞I/O和水平触发(select或poll)。select 和poll需要从文件描述符列表中,找出哪些可以执行 I/O ,然后进行真正的网络 I/O 读写。由于 I/O 是非阻塞的,一个线程中就可以同时监控一批套接字的文件描述符,这样就达到了单线程处理多请求的目的。 所以,这种方式的最大优点,是对应用程序比较友好,它的 API 非常简单。 但是,应用软件使用 select和poll时,需要对这些文件描述符列表进行轮询,这样请求数多的时候就会比较耗时。并且,select 和poll 还有一些其他的限制。select使用固定长度的位相量,表示文件描述符的集合,因此会有最大描述符数量的限 制。比如,在 32 位系统中,默认限制是 1024。并且,在 select 内部,检查套接字状态 是用轮询的方法,再加上应用软件使用时的轮询,就变成了一个 O(n^2) 的关系

poll 改进了 select 的表示方法,换成了一个没有固定长度的数组,这样就没有了最大 描述符数量的限制(当然还会受到系统文件描述符限制)。但应用程序在使用 poll 时,同 样需要对文件描述符列表进行轮询,这样,处理耗时跟描述符数量就是 O(N) 的关系;除此之外,应用程序每次调用 select 和poll时,还需要把文件描述符的集合,从用户空 间传入内核空间,由内核修改后,再传出到用户空间中。这一来一回的内核空间与用户空 间切换,也增加了处理成本

  1. 非阻塞I/O和边缘触发通知(epoll)
  • epoll 使用红黑树,在内核中管理文件描述符的集合,这样,就不需要应用程序在每次 操作时都传入、传出这个集合
  • epoll 使用事件驱动的机制,只关注有 I/O 事件发生的文件描述符,不需要轮询扫描整 个集合。
  1. 异步I/O glibc 的 aio 有 bug , kernel的aio 只能以 O_DIRECT方式做直接 IO , libeio也是beta阶段。epoll是成熟的,但是epoll本身是同步的。Linux上目前没有像 IOCP这样的成熟异步IO实现。
工作模型优化
  1. 主进程+多个worker子进程

主进程bind()+listen(),创建多个子进程;每个子进程,都通过accept()或epoll_wait()处理相同的套接字。 惊群问题:当网络 I/O 事件发生时,多个进程被同时唤醒,但实际上只有一个进程来响应这个事件,其他被 唤醒的进程都会重新休眠。为了避免惊群问题, Nginx 在每个 worker 进程中,都增加一个了全局锁 (accept_mutex)。这些 worker 进程需要首先竞争到锁,只有竞争到锁的进程,才会加 入到 epoll 中,这样就确保只有一个 worker 子进程被唤醒。

  1. 监听到相同端口的多进程模型 所有的进程都监听相同的接口, 并且开启 SO_REUSEPORT 选项,由内核负责将请求负载均衡到这些监听进程中去
C1000K问题
  • 物力资源:每个请求16KB,约15GB内存;万兆网卡
  • 软件资源:大量的连接也会占用大量的软件资源,比如文件描述符的数 量、连接状态的跟踪(CONNTRACK)、网络协议栈的缓存大小(比如套接字读写缓存、 TCP 读写缓存)等
  • 大量请求带来的中断处理,也会带来非常高的处理成本。这样,就需要多队列网 卡、中断负载均衡、CPU 绑定、RPS/RFS(软中断负载均衡到多个 CPU 核上),以及将 网络包的处理卸载(Offload)到网络设备(如 TSO/GSO、LRO/GRO、VXLAN OFFLOAD)等各种硬件和软件的优化
  • C1000K 的解决方法,本质上还是构建在 epoll 的非阻塞 I/O 模型上。只不过,除了 I/O 模型之外,还需要从应用程序到 Linux 内核、再到 CPU、内存和网络等各个层次的深度优 化,特别是需要借助硬件,来卸载那些原来通过软件处理的大量功能
C10M

C1000K 问题中,各种软件、硬件的优化很可能都已经做到头了。特别是当升 级完硬件(比如足够多的内存、带宽足够大的网卡、更多的网络功能卸载等)后,还是比较难实现的。究其根本,还是 Linux 内核协议栈做了太多太繁重的工作。从网卡中断带来的硬中断处理程序开始,到软中断中的各层网络协议处理,最后再到应用程序,这个路径实在是太长了,就会导致网络包的处理优化,到了一定程度后,就无法更进一步了。

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

  • 第一种机制,DPDK,是用户态网络的标准。它跳过内核协议栈,直接由用户态进程通过轮询的方式,来处理网络接收。在 PPS 非常高的场景中,查询时间比实际工作时间少了很多,绝大部分时间都在处理网 络包; 而跳过内核协议栈后,就省去了繁杂的硬中断、软中断再到 Linux网络协议栈逐层处理 的过程,应用程序可以针对应用的实际场景,有针对性地优化网络包的处理逻辑,而不 需要关注所有的细节。此外,DPDK还通过大页、CPU绑定、内存对齐、流水线并发等多种机制,优化网络包的 处理效率。
  • XDP(eXpress Data Path),则是 Linux 内核提供的一种高性能网络数据 路径。它允许网络包,在进入内核协议栈之前,就进行处理,也可以带来更高的性能。 XDP 底层跟我们之前用到的 bcc-tools 一样,都是基于 Linux 内核的 eBPF 机制实现的

XDP 对内核的要求比较高,需要的是 Linux 4.8 以上版本,并且它也不提供 缓存队列。基于 XDP 的应用程序通常是专用的网络应用,常见的有 IDS(入侵检测系 统)、DDoS 防御、 cilium 容器网络插件等

要实现 C10M,就不是增加物理资源、调优内核和应用程序可以解决的问题 了。这时内核中冗长的网络协议栈就成了最大的负担。

  • 需要用 XDP 方式,在内核协议栈之前,先处理网络包。
  • 或基于 DPDK,直接跳过网络协议栈,在用户空间通过轮询的方式处理。 其中,DPDK是目前最主流的高性能网络方案,不过,这需要能支持DPDK的网卡配合使用.

基准测试

  • 在应用层,你可以使用 wrk、Jmeter 等模拟用户的负载,测试应用程序的每秒请求数、 处理延迟、错误数等;
  • 在传输层,则可以使用 iperf 等工具,测试 TCP 的吞吐情况;
  • Linux 内核自带的 pktgen ,测试服务器的 PPS。


分享到:


相關文章: