如何优化网络性能?

836 阅读10分钟

了解计算机的都知道,计算机的操作系统管理着它的各种资源,其中就包括网络设备。可以说,网络设备相当于互联网的神经元,计算机依靠它互相通信。如果网络通信性能很差,会导致计算机的处理能力无法充分利用起来,进而影响我们所要设计的软件系统,所以优化网络性能就显得非常重要。

那么,在不增加网络设备成本的前提下,我们该如何提升网络性能呢?答案是可以通过优化系统参数来提升。

为什么?因为网络协议是由操作系统来处理的,而操作系统有很多网络协议相关的参数,如果这些参数配置错了,无疑会降低网络性能。

以秒杀系统为例,秒杀需要的各个商品图片通常是存放在图片存储系统中的。虽然这些图片一般会在前端缓存下来以便提升图片加载速度,但浏览器第一次访问的时候还是需要从图片存储系统获取图片。如果单个图片几MB 甚至几十MB,一旦过多,无疑会影响图片存储系统的性能。所以对于图片存储系统,我们可以通过优化网络连接缓冲区的相关参数来提升性能。

再比如秒杀接口,因为用户参与多,它需要处理大量秒杀请求,如果想要提升请求速度,可以通过优化快速处理网络连接和请求相关的参数来实现。

系统参数有哪些?

我们知道,Linux 系统参数有一千多个,这么多参数对应不同的作用,我们该如何选择合适的参数来优化呢?

首先,我们可以先找到所有的系统参数,在 Linux 系统中执行命令:

/sbin/sysctl -a

image.png 系统参数有很多,我这里只截取了一部分。

一般来说,不同的参数类型对应不同的资源和性能,进而影响不同的业务场景,比如网络参数主要影响以网络请求为主的业务,文件系统参数主要影响以读写文件为主的业务场景。

那我们该如何获取参数类型呢,可以执行以下命令:

/sbin/sysctl -a|awk -F "." '{print $1}'|sort -k1|uniq

image.png 其中 net 类型就是我们需要重点关注的,因为它几乎包含了网络性能优化相关的全部参数。通过执行以下命令:

/sbin/sysctl -a|grep "^net."|awk -F "[.| ]" '{print $2}'|sort -k1|uniq

我们将得到 net 类型下所有子类型:

image.png 在 Linux 系统里,这些参数都可以在 /etc/sysctl.conf 文件里修改,如果没有,你可以自行添加。修改完后,可以使用 sudo sysctl -p 命令加载最新配置,让配置生效。

在这些子类型当中,我们需要重点关注 core 和 ipv4 的配置。  因为这两类配置里包含网络协议相关的各种参数,如缓冲区内存大小参数、快速回收资源的参数等。前面我提到的秒杀接口服务和图片存储系统这两个业务场景,就需要 core 和 ipv4 中的参数来优化。

接下来,我们就来看下如何优化 core 和 ipv4 里非常关键的网络套接字缓冲区、TCP 协议、最大连接数等参数。

如何优化套接字缓冲区参数

/sbin/sysctl -a|grep "^net."|grep "[r|w|_]mem[_| ]"

结果如下:

image.png 上面的结果中,等号左边是参数名称,右边是参数的值。从等号左边的参数名称我们可以大致看出几个关键字:mem、rmem、wmem、max、default、min。

max、default、min 比较容易理解,分别是最大值、默认值、最小值。 mem、rmem、wmem 分别是总内存、接收缓冲区内存、发送缓冲区内存。

在上面那些内存参数里,rmem 和 wmem 的单位都是“字节”,而 mem 的单位是“页”。“页”是操作系统管理内存的最小单位,在 Linux 系统里,默认一页是 4KB 大小。

另外,你是否注意到,tcp_mem、tcp_rmem、tcp_wmem、udp_mem 这几个参数后面有三个值呢?对于 tcp_rmem 和 tcp_wmem 来说,这三个值是单个套接字可分配内存的大小,从左到右分别是最小值、默认值、最大值,如 tcp_rmem 的最小值是 4096、默认值是 131072、最大值是 6291456。

需要注意的是,这当中的默认值和最大值会分别被 net.core 下对应的 default 值和 max 值覆盖。比如 tcp_rmem 的默认值会被 rmem_default 覆盖,最大值将会被 rmem_max 覆盖。

对于 tcp_mem 和 udp_mem 来说,它后面的三个值用于控制内存压力,从左到右分别是内存压力的最小值、压力值、最大值,比如 tcp_mem 的最小值是 188964、压力值是251954、最大值是 377928。当 TCP 总内存使用量小于 188964 时,表示内存毫无压力,不用考虑回收;当内存使用量超过 251954 时,系统会开始回收内存,直到小于 188964;当内存使用量达到 377928 时,系统将会拒绝分配套接字,并输出日志“TCP: too many of orphaned sockets”。

那我们该如何优化这些参数呢?来看一个业务场景。

秒杀系统管理后台有个上传商品图片的功能,用于从前端上传图片到后端文件存储系统。商品图片小的也有上百 KB ,大的则达到十几 MB 。通常需要几秒甚至十几秒钟上传完一张图,如果批量上传,可能整个上传过程需要几十秒钟。对于这种文件上传的业务场景,该如何优化网络参数提升服务器的处理性能呢?

文件上传系统主要是负责处理文件数据,它不需要频繁处理建立和断开连接的请求,只需要尽快收发大量数据就行了。但因为每个数据包都比较大,我们需要为文件上传系统的每个套接字分配足够大的内存。

具体怎么做呢?我们知道,文件上传系统给前端提供的是 HTTP 接口,用的是 HTTP 协议,而 HTTP 协议底层是基于 TCP 连接传输数据的。所以,为了提升系统处理文件数据的性能,我们可以修改以下几个参数:

复制代码

net.core.rmem_default

net.core.rmem_max

net.core.wmem_default

net.core.wmem_max

net.ipv4.tcp_mem

net.ipv4.tcp_rmem

net.ipv4.tcp_wmem

假如系统最大可以为 TCP 分配 2GB 内存,最小值为 256MB,压力值为 1.5GB。按照一页为 4KB 来计算, tcp_mem 的最小值、压力值、最大值分别是 65536、393216、524288,单位是“页” 。

假如平均每个文件数据包为 512KB,每个套接字读写缓冲区最小可以各容纳 2 个数据包,默认可以各容纳 4 个数据包,最大可以各容纳 10 个数据包,那我们可以算出 tcp_rmem 和 tcp_wmem 的最小值、默认值、最大值分别是 1048576、2097152、5242880,单位是“字节”。而 rmem_default 和 wmem_default 是 2097152,rmem_max 和 wmem_max 是 5242880。

另外,由于缓冲区超过了 65535,还需要将 net.ipv4.tcp_window_scaling 参数设置为 1,告知系统使用大的 TCP 缓冲区。

最终,我们的参数配置如下:

net.core.rmem_default = 2097152
net.core.rmem_max = 5242880
net.core.wmem_default = 2097152
net.core.wmem_max = 5242880
net.ipv4.tcp_mem = 65536  393216  524288
net.ipv4.tcp_rmem = 1048576  2097152  5242880
net.ipv4.tcp_wmem = 1048576  2097152  5242880

需要注意的是,不同业务场景连接数的量级不同,缓冲区的配置也不同。对于秒杀接口这种大量短连接的业务场景,需要减少 rmem 和 wmem 相关的数值。比如将最小值、默认值、最大值分别改为 4096、4096、8192,就能建立更多的连接。

这是网络缓冲区的参数配置,接下来我给你介绍下 TCP 协议相关的参数。

如何优化 TCP 协议参数和最大连接数

熟悉 TCP 协议的人应该对 TCP 中的“三次握手”“四次挥手”“慢启动”“滑动窗口”“超时重传”“粘包算法”等机制不陌生,正是这些机制确保了 TCP 的可靠传输。但是,有时候,这些机制反而会成为网络瓶颈。

比如,当网络带宽非常好的时候,“慢启动”机制反而会限制数据传输速度。再比如,“粘包算法”会将一些小的数据包合并成一个 TCP 包发出去,或者一直等到定时器超时后发送。在某些情况下该算法确实能提升网络吞吐量,但对于一些对实时性要求较高的数据来说,它会导致接收方无法及时接收到数据。

那我们该如何优化这些机制的参数呢?我们来看下秒杀接口服务的业务场景。

秒杀的抢购接口,负责接收大量用户的抢购请求。对于有资格的用户,执行扣减库存并下单然后返回抢购成功给前端;对于没有资格或者扣减库存失败的用户,则返回抢购失败给前端。发起抢购请求以及请求返回的 HTTP 协议数据大概在 500 字节,秒杀接口服务需要快速处理连接的建立、断开、回收。那么,该如何优化秒杀接口服务的网络参数提升处理网络连接的性能呢?

首先,我们知道,秒杀接口服务是通过公网给用户提供服务的,而公网的数据帧能承载的应用数据是 1472 字节。秒杀抢购接口处理的数据大小 500 字节相当于 1472 的 1/3,由此可以判断这些数据包是小数据包,可能会受“粘包算法”影响。

然而,用户对请求的耗时很敏感,这就需要关闭“粘包算法”,确保数据包立即投递出去。怎么做呢?可以在 TCP 套接字加上 TCP_NODELAY 参数来关闭该算法。另外,为了抵御攻击者用大量 SYN 报文发起的短报文攻击,需要将 net.ipv4.tcp_syncookies 参数设置为 1。

第二,秒杀用户量大,这就需要秒杀接口服务处理大量短连接,由此会导致什么结果呢?需要秒杀接口服务创建、回收套接字的速度非常快,以便有足够资源处理大量连接。怎么办?我们可以通过关闭空闲连接、复用套接字等方法快速回收或重用已分配的资源。具体设置如下:

# 重用处于 TIME-WAIT 状态的套接字
net.ipv4.tcp_tw_reuse = 1
# 快速回收 TIME-WAIT 状态的套接字
net.ipv4.tcp_tw_recycle = 1
# 关闭处于 FIN-WAIT-2 状态 30 秒以上的套接字
net.ipv4.tcp_fin_timeout = 30
# 设置空闲 TCP 连接存活时间,以便即时关闭空闲连接,回收资源
net.ipv4.tcp_keepalive_time=1800

第三,由于秒杀请求量大,偶尔的网络抖动可能导致部分数据包丢失,这将会触发“超时重传”。为了避免重传网络抖动后的所有包,我们可以设置选择性重传的参数,避免重传已成功发送的数据包,导致浪费网络带宽。具体的办法是,将 net.ipv4.tcp_sack 这一参数设置为 1。

最后,秒杀活动中用户日活达到了百万以上,这就需要尽可能提升单机网络连接容量,确保并发能力。在操作系统中,一个网络连接会占用一个文件描述符,这需要将最大文件打开数的参数设置为一个比较大的值,以免文件描述符不够用导致性能问题。如 fs.file-max = 65535 表示最多可以打开 65535 个文件。