极客时间已完结课程限时免费阅读

43 | 套路篇:网络性能优化的几个思路(上)

43 | 套路篇:网络性能优化的几个思路(上)-极客时间

43 | 套路篇:网络性能优化的几个思路(上)

讲述:冯永吉

时长10:58大小10.01M

你好,我是倪朋飞。
上一节,我们了解了 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 服务来说,则需要同时兼顾吞吐量和延迟。
所以,为了更客观合理地评估优化效果,我们首先应该明确优化的标准,即要对系统和应用程序进行基准测试,得到网络协议栈各层的基准性能。
怎么评估系统的网络性能 中,我已经介绍过,网络性能测试的方法。简单回顾一下,Linux 网络协议栈,是我们需要掌握的核心原理。它是基于 TCP/IP 协议族的分层结构,我用一张图来表示这个结构。
明白了这一点,在进行基准测试时,我们就可以按照协议栈的每一层来测试。由于底层是其上方各层的基础,底层性能也就决定了高层性能。所以我们要清楚,底层性能指标,其实就是对应高层的极限性能。我们从下到上来理解这一点。
首先是网络接口层和网络层,它们主要负责网络包的封装、寻址、路由,以及发送和接收。每秒可处理的网络包数 PPS,就是它们最重要的性能指标(特别是在小包的情况下)。你可以用内核自带的发包工具 pktgen ,来测试 PPS 的性能。
再向上到传输层的 TCP 和 UDP,它们主要负责网络传输。对它们而言,吞吐量(BPS)、连接数以及延迟,就是最重要的性能指标。你可以用 iperf 或 netperf ,来测试传输层的性能。
不过要注意,网络包的大小,会直接影响这些指标的值。所以,通常,你需要测试一系列不同大小网络包的性能。
最后,再往上到了应用层,最需要关注的是吞吐量(BPS)、每秒请求数以及延迟等指标。你可以用 wrk、ab 等工具,来测试应用程序的性能。
不过,这里要注意的是,测试场景要尽量模拟生产环境,这样的测试才更有价值。比如,你可以到生产环境中,录制实际的请求情况,再到测试中回放。
总之,根据这些基准指标,再结合已经观察到的性能瓶颈,我们就可以明确性能优化的目标。

网络性能工具

同前面学习一样,我建议从指标和工具两个不同维度出发,整理记忆网络相关的性能工具。
第一个维度,从网络性能指标出发,你更容易把性能工具同系统工作原理关联起来,对性能问题有宏观的认识和把握。这样,当你想查看某个性能指标时,就能清楚知道,可以用哪些工具。
这里,我把提供网络性能指标的工具,做成了一个表格,方便你梳理关系和理解记忆。你可以把它保存并打印出来,随时查看。当然,你也可以把它当成一个“指标工具”指南来使用。
再来看第二个维度,从性能工具出发。这可以让你更快上手使用工具,迅速找出想要观察的性能指标。特别是在工具有限的情况下,我们更要充分利用好手头的每一个工具,用少量工具也要尽力挖掘出大量信息。
同样的,我也将这些常用工具,汇总成了一个表格,方便你区分和理解。自然,你也可以当成一个“工具指标”指南使用,需要时查表即可。

网络性能优化

总的来说,先要获得网络基准测试报告,然后通过相关性能工具,定位出网络性能瓶颈。再接下来的优化工作,就是水到渠成的事情了。
当然,还是那句话,要优化网络性能,肯定离不开 Linux 系统的网络协议栈和网络收发流程的辅助。你可以结合下面这张图再回忆一下这部分的知识。
接下来,我们就可以从应用程序、套接字、传输层、网络层以及链路层等几个角度,分别来看网络性能优化的基本思路。

应用程序

应用程序,通常通过套接字接口进行网络操作。由于网络收发通常比较耗时,所以应用程序的优化,主要就是对网络 I/O 和进程自身的工作模型的优化。
相关内容,其实我们在 C10K 和 C1000K 回顾 的文章中已经学过了。这里我们简单回顾一下。
从网络 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 网络性能优化方法。
在优化网络性能时,你可以结合 Linux 系统的网络协议栈和网络收发流程,然后从应用程序、套接字、传输层、网络层再到链路层等,进行逐层优化。
当然,其实我们分析、定位网络瓶颈,也是基于这些进行的。定位出性能瓶颈后,就可以根据瓶颈所在的协议层进行优化。比如,今天我们学了应用程序和套接字的优化思路:
在应用程序中,主要优化 I/O 模型、工作模型以及应用层的网络协议;
在套接字层中,主要优化套接字的缓冲区大小。
而其他各个网络层的优化方法,建议你先自己想一想,下一节,我们再来一起总结。

思考

最后,我想邀请你一起来聊聊,你在碰到网络的性能问题时,是怎么解决的?你可以结合今天的内容,从应用程序、套接字等方面,来总结自己的思路。
欢迎在留言区和我讨论,也欢迎把这篇文章分享给你的同事、朋友。我们一起在实战中演练,在交流中进步。
分享给需要的人,Ta购买本课程,你将得20
生成海报并分享

赞 13

提建议

上一篇
42 | 案例篇:如何优化 NAT 性能?(下)
下一篇
44 | 套路篇:网络性能优化的几个思路(下)
unpreview
 写留言

精选留言(29)

  • 分清云淡
    2019-03-01
    网络学习吃力的同学,先去把林沛满老师两本Wireshark分析网络看完,不建议一上来直接看TCP、IP两卷,之所以吃力就是纯理论看起来没有体感,理解不深,看完就忘记了。而WireShark书帮我们增加了体感
    共 4 条评论
    52
  • allan
    2019-03-30
    老师,您好,问一下 套接字接收发送缓冲区和TCP接收发送缓冲区,是相同的吗?我的理解这两个应该是相同的缓冲区,只是套接字选项的设置是针对单个套接字,而对 TCP接收发送缓冲区的设置,针对的是 全局的所有套接字 ,是这样理解吗?请老师指教。

    作者回复: 是的,内核选项的范围是全局的,套接字接口里面设置的是单个

    共 3 条评论
    12
  • 耿长学
    2019-07-20
    老师,我想咨询下tcp_mem满了如何排查,tcp_mem满了之后新的连接进不来,dmesg看到有tcp_mem:out of mem的提示

    作者回复: cat /proc/sys/net/ipv4/tcp_mem 查看配置 cat /proc/net/sockstat 查看当前tcp的统计 然后可以通过 sysctl -w net.ipv4.tcp_mem=新配置 来增大

    10
  • ninuxer
    2019-03-01
    打卡day45 套路篇总是干货满满,只能靠死记硬背了 网络是我的硬伤,而且看网络的书,比如 tcp/ip协议卷 总是没办法啃到底~最高峰也才看到一百六十多页😳
    8
  • 青石
    2019-03-20
    做视频业务时,公司产品Recorder用来收录组播视频源,收录下来的视频每间隔一段时间就会出现马赛克问题。 最初从日志和监控中,发现出现马赛克的时间点,进程在做写操作,同时监控上的磁盘IO也比较大,但并没有达到瓶颈。 后来发现网卡UDP的packet receive errors不断增大。和研发确认后,代码上接收和写是同一个进程。当时怀疑的原因是,使用同步IO导致进程阻塞,阻塞时间长导致接收缓慢,UDP缓存被占满直接丢包,调大UDP的缓冲区后,也只是缓解问题发生的频率,并没有解决。和研发提出改成异步IO,研发直接拒绝,现场自己想办法。 没办法,只能申请服务器,增加收录服务器数量、调大缓冲区,降低磁盘IO,减少问题出现的概率。
    展开
    6
  • 明翼
    2019-03-01
    跟上了,但是看的有点粗,这个系列我要多看几遍,性能问题无非CPU 内存 IO 网络,感谢老师的课程,对这类问题心理有底了,很喜欢去挑战问题了😁好想加老师微信,大神可以不😳

    作者回复: 我们专栏有个微信群可以加入

    共 7 条评论
    6
  • 夜空中最亮的星
    2019-03-01
    老师的套路总结,整理的表格太棒了!
    3
  • 腾达
    2019-03-01
    tcp_rmem、 tcp_wmem以及其他几个所对应的缓冲区的使用情况,有工具可以查看吗? 像类似“watch -d cat /proc/softirqs” 这样可以观察到缓冲区的变化。另外有图形界面工具吗?查看套接字缓冲区、TCP缓冲区大小变化的图形工具? 另外再问一下,套接字缓冲区、TCP缓冲区 这2个有什么区别吗?

    作者回复: 可以通过 /proc/net/sockstat、ss 这些工具来查看。图形界面也有很多,不过一般都是内置在监控系统里面,通过 Web 的方式来展示。比如 zabbix、Nagios等等

    4
  • xfan
    2019-03-02
    老师,我是用bpftools生成了一个过滤规则的字节码bytecode,bpfgen dns -- *.www.example.uk,这个规则怎么样加到网卡驱动上去呢,就是怎么安装到 linux18.04 上去呢.我知道load_bpf_file 这个方法,但是我在哪调用呢,老师给我一个思路,我想自己实现一套关于动态使用XDP拦截包的项目

    作者回复: 可以去参考一下XDP的文档https://prototype-kernel.readthedocs.io/en/latest/networking/XDP/index.html

    3
  • lupguo
    2020-04-29
    关于套接字缓存Buffer的补充下man tcp的信息: 1. 滑动窗口支持 > 64k的TCP窗口,支持高时延链接;默认缓冲区大小受全局/proc/sys/net/ipv4/tcp_wmem、/proc/sys/net/ipv4/tcp_rmem限制,针对单个套接字链接,可以基于SO_SNDBUF和SO_RCVBUF来设置(需要在调用connect或listen之前通过setsockopt设置); 2. 套接字的缓冲区SO_SNDBUF和SO_RCVBUFF最大值,受/proc/sys/net/core/rmem_max、/proc/sys/net/core/wmem_max内核限制 3. 通过setsockopt设置的缓存区实际内核分配的空间是两倍设置的大小 4. 不要随意设置套接字的SO_SNDBUF和SO_RCVBUFF,内核方面的自动调控效果比配置的要好,具体调整范围tcp_wmem [min,default,max]
    展开
    2
  • cliff(亮剑)
    2019-03-10
    老师好, 说实在,学了两边还是不明白如下数据之间的关系: SO_RCVBUFF, net/core/optmem_max, net/core/rmem_max, net/ipv4/tcp_rmem 和net/ipv4/tcp_mem 比如有如下的测试环境: 1)服务器端: 应用程序开了10个tcp服务器socket,每个设置10K SO_RCVBUFF 设置系统参数如下: net/core/optmem_max = 10K net/core/rmem_max =100K net/ipv4/tcp_rmem 4k 16K 80K net/ipv4/tcp_mem 40K 50K 120K 2)客户端轮流发送TCP消息给服务器: 那假设服务器的应用忙, 所有10个TCP的buffer能缓存100K的TCP数据么? 如果不能, 如何调整系统参数可以满足缓存100K的数据? 先谢谢老师
    展开

    作者回复: 内核配置选项是全局的,对所有socket生效;系统调用针对的是单个socket,他们的总和受限于内核配置

    1
  • J!
    2019-03-03
    对于kill -9 进程这样的情况,已建立的tcp连接如何处理,还是由操作系统主动回收?

    作者回复: 系统会回收掉

    2
  • 科学Jia
    2019-03-01
    老师,想问问您:现在我遇到应用程序在负载测试中可能处理速度慢造成了消息响应延迟,我该用什么方法或者工具去查看这个应用程序里每个方法的时间调用?像perf这个工具可以看每个调用方法的cpu利用率一样?

    作者回复: 实际上没有通用的方法,uprobe应该是可以用的,不过使用起来还是比较麻烦的。推荐的做法是在应用内部提供metrics 接口,这个接口内部统计每个方法的调用情况。

    2
  • 我来也
    2019-03-01
    [D43打卡] 又开始套路咯。好好收藏。 网络接收/发送流程 图片画的很直观。😄
    1
  • 怀特
    2019-03-01
    我之前做过一个优化项目。该项目需要实现一个C/S结构的程序,发送URL给服务器,服务器返回URL的类别,比如属于军事,还是属于体育。 当时在局域网环境下,性能较差,我经过分析之后,“感觉“瓶颈在网络,是因为每个包的有效负载太小,所以改进策略是将多个url拼接在一起再发送出去,然后解析返回的结果给每个url。拼接过程和解析过程都是我自己实现的。 听了倪工的讲座,再回顾当时的实现,能看到当时的不足:我找到了问题的症结所在,但却用了很笨的实现方法来改进。貌似在套接字层增加TCP_CORE,然后在应用程序层将处理改为异步非阻塞的,就可以了吧? 收获颇多,谢谢倪工。
    展开

    作者回复: 不一定非要异步,但非阻塞、epoll 是比较通用的网络性能优化方法

    1
  • Helios
    2021-10-12
    请问老师socket的缓冲区大小适用于Unix domain socket么
  • 永华
    2020-08-29
    老师请教一下,为什么Spring boot项目中,用curl去产生一个tomcat线程执行一个长时间任务(tomcat没有别的请求),会io明显忙于 jdk线程池起的一个线程做同样的任务,目前没有好的思路,我任务都是单线程执行理论时间都差不多,测试环境在没有任何访问的情况下也是同样的复现。访问方式是jpa访问的oracle。
    共 1 条评论
  • 妥协
    2020-06-08
    看了文章和留言,对套接字读写缓存区和TCP读写缓存区,还是有些疑问,老师留言回复中:内核选项的范围是全局的,套接字接口里面设置的是单个。而老师截图中的套接字内核选项列表,里面都是内核的。套接字读写缓存区和TCP读写缓存区是什么关系,有哪些差异呢。我理解是一个TCP连接对应一个读写缓存区,而UDP只有读缓存区,老师指的是指针全局的,我理解是对于每一个TCP连接或者UDP都是生效的,但是针对单个生效是啥意思?
    展开
    共 1 条评论
  • 南山
    2020-03-29
    网络基础理论相关的知识真的是硬伤!
  • 201200986
    2020-03-22
    实际中发现什么瓶颈点需要调节套接字的设置选项呢?我在实际的业务中发现网络带宽利用率很高,但是调节这些参数没有什么作用