43-套路篇:网络性能优化的几个思路(上)
确定优化目标
跟CPU和I/O方面的性能优化一样,优化前先考虑网络性能优化的目标是什么?
换句话说观察到的网络性能指标,要达到多少才合适呢?
实际上虽然网络性能优化的整体目标,是降低网络延迟(如RTT)和提高吞吐量(如BPS和PPS)
但具体到不同应用中,每个指标的优化标准可能会不同,优先级顺序也大相径庭
就拿上一节提到的NAT网关来说,由于其直接影响整个数据中心的网络出入性能
所以NAT网关通常需要达到或接近线性转发,也就是说PPS是最主要的性能目标
再如对于数据库、缓存等系统,快速完成网络收发,即低延迟,是主要的性能目标
而对于Web服务来说,则需要同时兼顾吞吐量和延迟
所以为了更客观合理地评估优化效果
首先应该明确优化的标准,即要对系统和应用程序进行基准测试,得到网络协议栈各层的基准性能
Linux网络协议栈是需要掌握的核心原理,它是基于TCP/IP协议族的分层结构
明白了这一点,在进行基准测试时就可以按照协议栈的每一层来测试
由于底层是其上方各层的基础,底层性能也就决定了高层性能
所以要清楚,底层性能指标,其实就是对应高层的极限性能
首先是网络接口层和网络层,它们主要负责网络包的封装、寻址、路由以及发送和接收
每秒可处理的网络包数PPS,就是它们最重要的性能指标(特别是在小包的情况下)
可以用内核自带的发包工具pktgen ,来测试PPS的性能
再向上到传输层的TCP和UDP,它们主要负责网络传输
对它们而言吞吐量(BPS)、连接数以及延迟,就是最重要的性能指标
可以用iperf或netperf ,来测试传输层的性能
不过要注意网络包的大小,会直接影响这些指标的值
所以,通常需要测试一系列不同大小网络包的性能
最后再往上到了应用层,最需要关注的是吞吐量(BPS)、每秒请求数以及延迟等指标
用wrk、ab等工具来测试应用程序的性能
这里要注意的是,测试场景要尽量模拟生产环境,这样的测试才更有价值
比如可以到生产环境中,录制实际的请求情况,再到测试中回放
网络性能工具
- 第一个维度从网络性能指标出发更容易把性能工具同系统工作原理关联起来,对性能问题有宏观认识和把握
这样当想查看某个性能指标时,就能清楚知道,可以用哪些工具
- 第二个维度,从性能工具出发
这可以更快上手使用工具,迅速找出想要观察的性能指标
特别是在工具有限的情况下,更要充分利用好手头的每一个工具,用少量工具也要尽力挖掘出大量信息
网络性能优化
总的来说,先要获得网络基准测试报告,然后通过相关性能工具,定位出网络性能瓶颈
再接下来的优化工作,就是水到渠成的事情了
当然还是那句话,要优化网络性能,肯定离不开Linux系统的网络协议栈和网络收发流程的辅助
可以结合下面这张图再回忆一下这部分的知识
应用程序优化
应用程序通常通过套接字接口进行网络操作
由于网络收发通常比较耗时,所以应用程序的优化,主要就是对网络I/O和进程自身的工作模型的优化
从网络I/O的角度来说,主要有下面两种优化思路
- 第一种是最常用的I/O多路复用技术epoll,主要用来取代select和poll
这其实是解决C10K问题的关键,也是目前很多网络应用默认使用的机制 - 第二种是使用异步I/O(Asynchronous I/O AIO)
AIO允许应用程序同时发起很多I/O操作,而不用等待这些操作完成
等到I/O完成后,系统会用事件通知的方式,告诉应用程序结果
不过,AIO的使用比较复杂,需要小心处理很多边缘情况
从进程的工作模型来说,也有两种不同的模型用来优化
- 第一种,主进程+多个worker子进程
其中主进程负责管理网络连接,而子进程负责实际的业务处理
这也是最常用的一种模型 - 第二种,监听到相同端口的多进程模型
在这种模型下,所有进程都会监听相同接口
并且开启SO_REUSEPORT选项,由内核负责,把请求负载均衡到这些监听进程中去
除了网络 I/O 和进程的工作模型外,应用层的网络协议优化,也是至关重要的一点
常见的几种优化方法
- 使用长连接取代短连接,可以显著降低TCP建立连接的成本
在每秒请求次数较多时, 这样做的效果非常明显 - 使用内存等方式,来缓存不常变化的数据,可以降低网络I/O次数,同时加快应用程序的响应速度
- 使用Protocol Buffer等序列化的方式,压缩网络I/O的数据量,可以提高应用程序的吞吐
- 使用DNS缓存、预取、HTTPDNS等方式,减少DNS解析的延迟,也可以提升网络I/O的整体速度
套接字
套接字可以屏蔽掉Linux内核中不同协议的差异,为应用程序提供统一的访问接口
每个套接字,都有一个读写缓冲区
- 读缓冲区,缓存了远端发过来的数据
如果读缓冲区已满,就不能再接收新的数据 - 写缓冲区,缓存了要发出去的数据
如果写缓冲区已满,应用程序的写操作就会被阻 塞。
所以,为了提高网络的吞吐量,你通常需要调整这些缓冲区的大小
- 增大每个套接字的缓冲区大小net.core.optmem_max
- 增大套接字接收缓冲区大小net.core.rmem_max和发送缓冲区大小net.core.wmem_max
- 增大TCP接收缓冲区大小net.ipv4.tcp_rmem和发送缓冲区大小net.ipv4.tcp_wmem
至于套接字的内核选项,参考下表
有几点需要注意
- tcp_rmem和tcp_wmem的三个数值分别是min,default,max
系统会根据这些设置自动调整TCP接收/发送缓冲区的大小 - udp_mem的三个数值分别是min,pressure,max
系统会根据这些设置,自动调整UDP发送缓冲区的大小
当然,表格中的数值只提供参考价值,具体应该设置多少,还需要根据实际的网络状况来确定
比如,发送缓冲区大小,理想数值是吞吐量 * 延迟,这样才可以达到最大网络利用率
除此之外,套接字接口还提供了一些配置选项,用来修改网络连接的行为
- 为TCP连接设置TCP_NODELAY后,就可以禁用Nagle算法
- 为TCP连接开启TCP_CORK后,可以让小包聚合成大包后再发送(注意会阻塞小包的发送)
- 使用SO_SNDBUF和SO_RCVBUF ,可以分别调整套接字发送缓冲区和接收缓冲区的大小
小结
在优化网络性能时,可以结合Linux系统的网络协议栈和网络收发流程
然后从应用程序、套接字、传输层、网络层再到链路层等进行逐层优化
当然其实分析定位网络瓶颈,也是基于这些进行的
定位出性能瓶颈后,就可以根据瓶颈所在的协议层进行优化
比如应用程序和套接字的优化思路
- 在应用程序中,主要优化I/O模型、工作模型以及应用层的网络协议
- 在套接字层中,主要优化套接字的缓冲区大小