You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

166 lines
12 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 43 | 套路篇:网络性能优化的几个思路(上)
你好,我是倪朋飞。
上一节我们了解了NAT网络地址转换的原理学会了如何排查 NAT 带来的性能问题,最后还总结了 NAT 性能优化的基本思路。我先带你简单回顾一下。
NAT 基于 Linux 内核的连接跟踪机制,实现了 IP 地址及端口号重写的功能,主要被用来解决公网 IP 地址短缺的问题。
在分析 NAT 性能问题时,可以先从内核连接跟踪模块 conntrack 角度来分析,比如用 systemtap、perf、netstat 等工具,以及 proc 文件系统中的内核选项,来分析网络协议栈的行为;然后,通过内核选项调优、切换到无状态 NAT、使用 DPDK 等方式,进行实际优化。
通过前面的学习,你应该已经体会到,网络问题比我们前面学过的 CPU、内存或磁盘 I/O 都要复杂。无论是应用层的各种 I/O 模型,冗长的网络协议栈和众多的内核选项,抑或是各种复杂的网络环境,都提高了网络的复杂性。
不过,也不要过分担心,只要你掌握了 Linux 网络的基本原理和常见网络协议的工作流程,再结合各个网络层的性能指标来分析,你会发现,定位网络瓶颈并不难。
找到网络性能瓶颈后,下一步要做的就是优化了,也就是如何降低网络延迟,并提高网络的吞吐量。学完相关原理和案例后,我就来讲讲,优化网络性能问题的思路和一些注意事项。
由于网络优化思路的内容比较多,我们分两节来学习,今天我们先来看上篇。
## 确定优化目标
跟 CPU 和 I/O 方面的性能优化一样,优化前,我会先问问自己,网络性能优化的目标是什么?换句话说,我们观察到的网络性能指标,要达到多少才合适呢?
实际上,虽然网络性能优化的整体目标,是降低网络延迟(如 RTT和提高吞吐量如 BPS 和 PPS但具体到不同应用中每个指标的优化标准可能会不同优先级顺序也大相径庭。
就拿上一节提到的 NAT 网关来说,由于其直接影响整个数据中心的网络出入性能,所以 NAT 网关通常需要达到或接近线性转发,也就是说, PPS 是最主要的性能目标。
再如,对于数据库、缓存等系统,快速完成网络收发,即低延迟,是主要的性能目标。
而对于我们经常访问的 Web 服务来说,则需要同时兼顾吞吐量和延迟。
所以,为了更客观合理地评估优化效果,我们首先应该明确优化的标准,即要对系统和应用程序进行基准测试,得到网络协议栈各层的基准性能。
在 [怎么评估系统的网络性能](https://time.geekbang.org/column/article/81497) 中我已经介绍过网络性能测试的方法。简单回顾一下Linux 网络协议栈,是我们需要掌握的核心原理。它是基于 TCP/IP 协议族的分层结构,我用一张图来表示这个结构。
![](https://static001.geekbang.org/resource/image/c7/ac/c7b5b16539f90caabb537362ee7c27ac.png)
明白了这一点,在进行基准测试时,我们就可以按照协议栈的每一层来测试。由于底层是其上方各层的基础,底层性能也就决定了高层性能。所以我们要清楚,底层性能指标,其实就是对应高层的极限性能。我们从下到上来理解这一点。
首先是网络接口层和网络层,它们主要负责网络包的封装、寻址、路由,以及发送和接收。每秒可处理的网络包数 PPS就是它们最重要的性能指标特别是在小包的情况下。你可以用内核自带的发包工具 pktgen ,来测试 PPS 的性能。
再向上到传输层的 TCP 和 UDP它们主要负责网络传输。对它们而言吞吐量BPS、连接数以及延迟就是最重要的性能指标。你可以用 iperf 或 netperf ,来测试传输层的性能。
不过要注意,网络包的大小,会直接影响这些指标的值。所以,通常,你需要测试一系列不同大小网络包的性能。
最后再往上到了应用层最需要关注的是吞吐量BPS、每秒请求数以及延迟等指标。你可以用 wrk、ab 等工具,来测试应用程序的性能。
不过,这里要注意的是,测试场景要尽量模拟生产环境,这样的测试才更有价值。比如,你可以到生产环境中,录制实际的请求情况,再到测试中回放。
总之,根据这些基准指标,再结合已经观察到的性能瓶颈,我们就可以明确性能优化的目标。
## 网络性能工具
同前面学习一样,我建议从指标和工具两个不同维度出发,整理记忆网络相关的性能工具。
第一个维度,从网络性能指标出发,你更容易把性能工具同系统工作原理关联起来,对性能问题有宏观的认识和把握。这样,当你想查看某个性能指标时,就能清楚知道,可以用哪些工具。
这里,我把提供网络性能指标的工具,做成了一个表格,方便你梳理关系和理解记忆。你可以把它保存并打印出来,随时查看。当然,你也可以把它当成一个“指标工具”指南来使用。
![](https://static001.geekbang.org/resource/image/a1/3b/a1eb07e281e5795be83c11d7255c543b.png)
再来看第二个维度,从性能工具出发。这可以让你更快上手使用工具,迅速找出想要观察的性能指标。特别是在工具有限的情况下,我们更要充分利用好手头的每一个工具,用少量工具也要尽力挖掘出大量信息。
同样的,我也将这些常用工具,汇总成了一个表格,方便你区分和理解。自然,你也可以当成一个“工具指标”指南使用,需要时查表即可。
![](https://static001.geekbang.org/resource/image/0d/a0/0d87b39b89a1b7f325fc5477c0182ea0.png)
## 网络性能优化
总的来说,先要获得网络基准测试报告,然后通过相关性能工具,定位出网络性能瓶颈。再接下来的优化工作,就是水到渠成的事情了。
当然,还是那句话,要优化网络性能,肯定离不开 Linux 系统的网络协议栈和网络收发流程的辅助。你可以结合下面这张图再回忆一下这部分的知识。
![](https://static001.geekbang.org/resource/image/a1/3f/a118911721f9b67ce9c83de15666753f.png)
接下来,我们就可以从应用程序、套接字、传输层、网络层以及链路层等几个角度,分别来看网络性能优化的基本思路。
### 应用程序
应用程序,通常通过套接字接口进行网络操作。由于网络收发通常比较耗时,所以应用程序的优化,主要就是对网络 I/O 和进程自身的工作模型的优化。
相关内容,其实我们在 [C10K 和 C1000K 回顾](https://time.geekbang.org/column/article/81268) 的文章中已经学过了。这里我们简单回顾一下。
从网络 I/O 的角度来说,主要有下面两种优化思路。
第一种是最常用的 I/O 多路复用技术 epoll主要用来取代 select 和 poll。这其实是解决 C10K 问题的关键,也是目前很多网络应用默认使用的机制。
第二种是使用异步 I/OAsynchronous I/OAIO。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。
至于套接字的内核选项,我把它们整理成了一个表格,方便你在需要时参考:
![](https://static001.geekbang.org/resource/image/5f/f0/5f2d4957663dd8bf3410da8180ab18f0.png)
不过有几点需要你注意。
* tcp\_rmem 和 tcp\_wmem 的三个数值分别是 mindefaultmax系统会根据这些设置自动调整TCP接收/发送缓冲区的大小。
* udp\_mem 的三个数值分别是 minpressuremax系统会根据这些设置自动调整UDP发送缓冲区的大小。
当然,表格中的数值只提供参考价值,具体应该设置多少,还需要你根据实际的网络状况来确定。比如,发送缓冲区大小,理想数值是吞吐量\*延迟,这样才可以达到最大网络利用率。
除此之外,套接字接口还提供了一些配置选项,用来修改网络连接的行为:
* 为 TCP 连接设置 TCP\_NODELAY 后,就可以禁用 Nagle 算法;
* 为 TCP 连接开启 TCP\_CORK 后,可以让小包聚合成大包后再发送(注意会阻塞小包的发送);
* 使用 SO\_SNDBUF 和 SO\_RCVBUF ,可以分别调整套接字发送缓冲区和接收缓冲区的大小。
## 小结
今天,我们一起梳理了常见的 Linux 网络性能优化方法。
在优化网络性能时,你可以结合 Linux 系统的网络协议栈和网络收发流程,然后从应用程序、套接字、传输层、网络层再到链路层等,进行逐层优化。
当然,其实我们分析、定位网络瓶颈,也是基于这些进行的。定位出性能瓶颈后,就可以根据瓶颈所在的协议层进行优化。比如,今天我们学了应用程序和套接字的优化思路:
* 在应用程序中,主要优化 I/O 模型、工作模型以及应用层的网络协议;
* 在套接字层中,主要优化套接字的缓冲区大小。
而其他各个网络层的优化方法,建议你先自己想一想,下一节,我们再来一起总结。
## 思考
最后,我想邀请你一起来聊聊,你在碰到网络的性能问题时,是怎么解决的?你可以结合今天的内容,从应用程序、套接字等方面,来总结自己的思路。
欢迎在留言区和我讨论,也欢迎把这篇文章分享给你的同事、朋友。我们一起在实战中演练,在交流中进步。