linux之TCP调优
# 原⽂
原⽂地址
# tcp握⼿
客户端在等待服务器回复的 ACK 报⽂。正常情况下,服务器会在⼏毫秒内返回 ACK,但如果客户端迟迟没有收到 ACK 客户端会重发SYN,重试的次数由 tcp_syn_retries 参数控制,默认是 6 次:
p_syn_retries = 6
第 1 次重试发⽣在 1 秒钟后,接着会以翻倍的⽅式在第 2、4、8、16、32 秒共做 6 次重试,最后⼀次重试会等待 64 秒,如果仍然没有返回ACK,才会终⽌三次握⼿。所以,总耗时是 1+2+4+8+16+32+64=127 秒,超过 2 分钟。
九年级上册英语翻译如果这是⼀台有明确任务的服务器,你可以根据⽹络的稳定性和⽬标服务器的繁忙程度修改重试次数,调整客户端的三次握⼿时间上限。⽐如内⽹中通讯时,就可以适当调低重试次数,尽快把错误暴露给应⽤程序。
新连接建⽴失败的原因有很多,怎样获得由于队列已满⽽引发的失败次数呢?netstat -s 命令给出的统计结果中可以得到:
netstat -s | grep "SYNs to LISTEN"
这⾥给出的是队列溢出导致 SYN 被丢弃的个数。注意这是⼀个累计值,如果数值在持续增加,则应该调⼤ SYN 半连接队列。修改队列⼤⼩的⽅法,是设置 Linux 的 tcp_max_syn_backlog 参数:
p_max_syn_backlog = 1024
这⾥给出的是队列溢出导致 SYN 被丢弃的个数。注意这是⼀个累计值,如果数值在持续增加,则应该调⼤ SYN 半连接队列。修改队列⼤⼩的⽅法,是设置 Linux 的 tcp_max_syn_backlog 参数:
p_max_syn_backlog = 1024
如果 SYN 半连接队列已满,只能丢弃连接吗?并不是这样,开启 syncookies 功能就可以在不使⽤ SYN 队列的情况下成功建⽴连接。syncookies 是这么做的:服务器根据当前状态计算出⼀个值,放在⼰⽅发出的 SYN+ACK 报⽂中发出,当客户端返回 ACK 报⽂时,取出该值验证,如果合法,就认为连接建⽴成功
Linux 下怎样开启 syncookies 功能呢?修改 tcp_syncookies 参数即可,其中值为 0 时表⽰关闭该功能,2 表⽰⽆条件开启功能,⽽ 1 则表⽰仅当 SYN 半连接队列放不下时,再启⽤它。由于 syncookie 仅⽤于应对 SYN 泛洪攻击(攻击者恶意构造⼤量的 SYN 报⽂发送给服务器,造成 SYN 半连接队列溢出,导致正常客户端的连接⽆法建⽴),这种⽅式建⽴的连接,许多 TCP 特性都⽆法使⽤。所以,应当把
tcp_syncookies 设置为 1,仅在队列满时再启⽤。
p_syncookies = 1
当客户端接收到服务器发来的 SYN+ACK 报⽂后,就会回复 ACK 去通知服务器,同时⼰⽅连接状态从 SYN_SENT 转换为ESTABLISHED,表⽰连接建⽴成功。服务器端连接成功建⽴的时间还要再往后,到它收到 ACK 后状态才变为 ESTABLISHED。
如果服务器没有收到 ACK,就会⼀直重发 SYN+ACK 报⽂。当⽹络繁忙、不稳定时,报⽂丢失就会变严重,此时应该调⼤重发次数。反之则可以调⼩重发次数。修改重发次数的⽅法是,调整 tcp_synack_retries 参数:
p_synack_retries = 5
tcp_synack_retries 的默认重试次数是 5 次,与客户端重发 SYN 类似,它的重试会经历 1、2、4、8、16 秒,最后⼀次重试后等待 32 秒,若仍然没有收到 ACK,才会关闭连接,故共需要等待 63 秒。
服务器收到 ACK 后连接建⽴成功,此时,内核会把连接从 SYN 半连接队列中移出,再移⼊ accept 队列,等待进程调⽤ accept 函数时把连接取出来。如果进程不能及时地调⽤ accept 函数,就会造成 accept 队列溢出,最终导致建⽴好的 TCP 连接被丢弃。
实际上,丢弃连接只是 Linux 的默认⾏为,我们还可以选择向客户端发送 RST 复位报⽂,告诉客户端连接已经建⽴失败。打开这⼀功能需要将 tcp_abort_on_overflow 参数设置为 1。
p_abort_on_overflow = 0
通常情况下,应当把 tcp_abort_on_overflow 设置为 0,因为这样更有利于应对突发流量。举个例⼦,当 accept 队列满导致服务器丢掉了ACK,与此同时,客户端的连接状态却是 ESTABLISHED,进程就在建⽴好的连接上发送请求。只要服务器没有为请求回复 ACK,请求就
会被多次重发。如果服务器上的进程只是短暂的繁忙造成 accept 队列满,那么当 accept 队列有空位时,再次接收到的请求报⽂由于含有ACK,仍然会触发服务器端成功建⽴连接。所以,tcp_abort_on_
中国老人tueoldan
overflow 设为 0 可以提⾼连接建⽴的成功率,只有你⾮常肯定 accept 队列会长期溢出时,才能设置为 1 以尽快通知客户端。
那么,怎样调整 accept 队列的长度呢?listen 函数的 backlog 参数就可以设置 accept 队列的⼤⼩。事实上,backlog 参数还受限于 Linux 系统级的队列长度上限,当然这个上限阈值也可以通过 somaxconn 参数修改:
大学英语作文大全
当下各监听端⼝上的 accept 队列长度可以通过 ss -ltn 命令查看,但 accept 队列长度是否需要调整该怎么判断呢?还是通过 netstat -s 命令给出的统计结果,可以看到究竟有多少个连接因为队列溢出⽽被丢弃:
netstat -s | grep "listen queue"
如果持续不断地有连接因为 accept 队列溢出被丢弃,就应该调⼤ backlog 以及 somaxconn 参数。
Linux 下怎么打开 TFO 功能呢?这要通过 tcp_fastopen 参数。由于只有客户端和服务器同时⽀持时,TFO 功能才能使⽤,所以
tcp_fastopen 参数是按⽐特位控制的。其中,第 1 个⽐特位为 1 时,表⽰作为客户端时⽀持 TFO;第 2 个⽐特位为 1 时,表⽰作为服务器时⽀持 TFO,所以当 tcp_fastopen 的值为 3 时(⽐特为 0x11)就表⽰完全⽀持 TFO 功能:
p_fastopen = 3
# tcp挥⼿convenient是什么意思
clo与shutdown
clo 和 shutdown 函数都可以关闭连接,但这两种⽅式关闭的连接,不只功能上有差异,控制它们的 Linux 参数也不相同。clo 函数会让连接变为孤⼉连接,shutdown 函数则允许在半关闭的连接上长时间传输数据。TCP 之所以具备这个功能,是因为它是全双⼯协议,但这也造成四次挥⼿⾮常复杂。
安全关闭连接的⽅式必须通过四次挥⼿,它由进程调⽤ clo 或者 shutdown 函数发起,这⼆者都会向对⽅发送 FIN 报⽂(shutdown 参数须传⼊ SHUT_WR 或者 SHUT_RDWR 才会发送 FIN),区别在于 clo 调⽤后,哪怕对⽅在半关闭状态下发送的数据到达主动⽅,进程也⽆法接收。
此时,这个连接叫做孤⼉连接,如果你⽤ netstat -p 命令,会发现连接对应的进程名为空。⽽ shutdown 函数调⽤后,即使连接进⼊了
FIN_WAIT1 或者 FIN_WAIT2 状态,它也不是孤⼉连接,进程仍然可以继续接收数据。关于孤⼉连接的概念,下⽂调优参数时还会⽤到。为什么需要四次挥⼿
当主动⽅关闭连接时,被动⽅仍然可以在不调⽤ clo 函数的状态下,长时间发送数据,此时连接处于半关闭状态。这⼀特性是 TCP 的双向通道互相独⽴所致,却也使得关闭连接必须通过四次挥⼿才能做到。
其实四次挥⼿只涉及两种报⽂:FIN 和 ACK。FIN 就是 Finish 结束连接的意思,谁发出 FIN 报⽂,就表⽰它将不再发送任何数据,关闭这⼀⽅向的传输通道。ACK 是 Acknowledge 确认的意思,它⽤来通知对⽅:你⽅的发送通道已经关闭。
ACK重试次数
主动⽅发送 FIN 报⽂后,连接就处于 FIN_WAIT1 状态下,该状态通常应在数⼗毫秒内转为 FIN_WAIT2。只有迟迟收不到对⽅返回的 ACK 时,才能⽤ netstat 命令观察到 FIN_WAIT1 状态。此时,内核会定时重发 FIN 报⽂,其中重发次数由 tcp_orphan_retries 参数控制(注意,orphan 虽然是孤⼉的意思,该参数却不只对孤⼉连接有效,事实上,它对所有 FIN_WAIT1 状态下的连接都有效),默认值是 0,特指8 次:
p_orphan_retries = 0
sbr
如果 FIN_WAIT1 状态连接有很多,你就需要考虑降低 tcp_orphan_retries 的值。当重试次数达到 tcp_orphan_retries 时,连接就会直接关闭掉。
一般现在时讲解对于正常情况来说,调低 tcp_orphan_retries 已经够⽤,但如果遇到恶意攻击,FIN 报⽂根本⽆法发送出去。这是由 TCP 的 2 个特性导致的。
⾸先,TCP 必须保证报⽂是有序发送的,FIN 报⽂也不例外,当发送缓冲区还有数据没发送时,FIN 报⽂也不能提前发送。
其次,TCP 有流控功能,当接收⽅将接收窗⼝设为 0 时,发送⽅就不能再发送数据。所以,当攻击者下载⼤⽂件时,就可以通过将接收窗⼝设为 0,导致 FIN 报⽂⽆法发送,进⽽导致连接⼀直处于 FIN_WAIT1 状态
解决这种问题的⽅案是调整 tcp_max_orphans 参数:
p_max_orphans = 16384
顾名思义,tcp_max_orphans 定义了孤⼉连接的最⼤数量。当进程调⽤ clo 函数关闭连接后,⽆论该连接是在 FIN_WAIT1 状态,还是确实关闭了,这个连接都与该进程⽆关了,它变成了孤⼉连接。Linux 系统为防⽌孤⼉连接过多,导致系统资源长期被占⽤,就提供了
tcp_max_orphans 参数。如果孤⼉连接数量⼤于它,新增的孤⼉连接将不再⾛四次挥⼿,⽽是直接发送 RST 复位报⽂强制关闭。
当连接收到 ACK 进⼊ FIN_WAIT2 状态后,就表⽰主动⽅的发送通道已经关闭,接下来将等待对⽅发送 FIN 报⽂,关闭对⽅的发送通道。这时,如果连接是⽤ shutdown 函数关闭的,连接可以⼀直处于 FIN_WAIT2 状态。但对于 clo 函数关闭的孤⼉连接,这个状态不可以持续太久,⽽ tcp_fin_timeout 控制了这个状态下连接的持续时长。
p_fin_timeout = 60
它的默认值是 60 秒。这意味着对于孤⼉连接,如果 60 秒后还没有收到 FIN 报⽂,连接就会直接关闭。这个 60 秒并不是拍脑袋决定的,它与接下来介绍的 TIME_WAIT 状态的持续时间是相同的,我们稍后再来回答 60 秒的由来。
TIME_WAIT 是主动⽅四次挥⼿的最后⼀个状态。当收到被动⽅发来的 FIN 报⽂时,主动⽅回复 ACK,表⽰确认对⽅的发送通道已经关闭,连接随之进⼊ TIME_WAIT 状态,等待 60 秒后关闭,为什么呢?我们必须站在整个⽹络的⾓度上,才能回答这个问题。
TIME_WAIT 状态的连接,在主动⽅看来确实已经关闭了。然⽽,被动⽅没有收到 ACK 报⽂前,连接
还处于 LAST_ACK 状态。如果这个ACK 报⽂没有到达被动⽅,被动⽅就会重发 FIN 报⽂。重发次数仍然由前⾯介绍过的 tcp_orphan_retries 参数控制。
如果主动⽅不保留 TIME_WAIT 状态,会发⽣什么呢?此时连接的端⼝恢复了⾃由⾝,可以复⽤于新连接了。然⽽,被动⽅的 FIN 报⽂可能再次到达,这既可能是⽹络中的路由器重复发送,也有可能是被动⽅没收到 ACK 时基于 tcp_orphan_retries 参数重发。这样,正常通讯的新连接就可能被重复发送的 FIN 报⽂误关闭。保留 TIME_WAIT 状态,就可以应付重发的 FIN 报⽂,当然,其他数据报⽂也有可能重发,所以 TIME_WAIT 状态还能避免数据错乱。
我们再回过头来看看,为什么 TIME_WAIT 状态要保持 60 秒呢?这与孤⼉连接 FIN_WAIT2 状态默认保留 60 秒的原理是⼀样的,因为这两个状态都需要保持 2MSL 时长。MSL 全称是 Maximum Segment Lifetime,它定义了⼀个报⽂在⽹络中的最长⽣存时间(报⽂每经过⼀次路由器的转发,IP 头部的 TTL 字段就会减 1,减到 0 时报⽂就被丢弃,这就限制了报⽂的最长存活时间)。
为什么是 2 MSL 的时长呢?这其实是相当于⾄少允许报⽂丢失⼀次。⽐如,若 ACK 在⼀个 MSL 内丢失,这样被动⽅重发的 FIN 会在第 2个 MSL 内到达,TIME_WAIT 状态的连接可以应对。为什么不是 4 或者 8 MSL 的时长呢?你可以想象⼀个丢包率达到百分之⼀的糟糕⽹络,连续两次丢包的概率只有万分之⼀,这个概率实在是太⼩了,忽略它⽐解决它更具性价⽐。
因此,TIME_WAIT 和 FIN_WAIT2 状态的最⼤时长都是 2 MSL,由于在 Linux 系统中,MSL 的值固定为 30 秒,所以它们都是 60 秒。日译中翻译>goal是什么意思
虽然 TIME_WAIT 状态的存在是有必要的,但它毕竟在消耗系统资源,⽐如 TIME_WAIT 状态的端⼝就⽆法供新连接使⽤。怎样解决这个问题呢?zhujie
Linux 提供了 tcp_max_tw_buckets 参数,当 TIME_WAIT 的连接数量超过该参数时,新关闭的连接就不再经历 TIME_WAIT ⽽直接关闭。p_max_tw_buckets = 5000
当服务器的并发连接增多时,相应地,同时处于 TIME_WAIT 状态的连接数量也会变多,此时就应当调⼤ tcp_max_tw_buckets 参数,减少不同连接间数据错乱的概率。
当然,tcp_max_tw_buckets 也不是越⼤越好,毕竟内存和端⼝号都是有限的。有没有办法让新连接复⽤ TIME_WAIT 状态的端⼝呢?如果服务器会主动向上游服务器发起连接的话,就可以把 tcp_tw_reu 参数设置为 1,它允许作为客户端的新连接,在安全条件下使⽤
TIME_WAIT 状态下的端⼝。
p_tw_reu = 1
当然,要想使 tcp_tw_reu ⽣效,还得把 timestamps 参数设置为 1,它满⾜安全复⽤的先决条(对⽅也要打开 tcp_timestamps ):
p_timestamps = 1
⽼版本的 Linux 还提供了 tcp_tw_recycle 参数,它并不要求 TIME_WAIT 状态存在 60 秒,很容易导致数据错乱,不建议设置为 1:
p_tw_recycle = 0
所以在 Linux 4.12 版本后,直接取消了这⼀参数。
# tcp缓冲区
概述
如果你在 Linux 系统中⽤ free 命令查看内存占⽤情况,会发现⼀栏叫做 buff/cache,它是系统内存,似乎与应⽤进程⽆关。但每当进程新建
⼀个 TCP 连接,buff/cache 中的内存都会上升 4K 左右。⽽且,当连接传输数据时,就远不⽌增加 4K 内存了。这样,⼏⼗万并发连接,就在进程内存外⼜增加了 GB 级别的系统内存消耗。
这是因为 TCP 连接是由内核维护的,内核为每个连接建⽴的内存缓冲区,既要为⽹络传输服务,也要充当进程与⽹络间的缓冲桥梁。如果连接的内存配置过⼩,就⽆法充分使⽤⽹络带宽,TCP 传输速度就会很慢;如果连接的内存配置过⼤,那么服务器内存会很快⽤尽,新连接就⽆法建⽴成功。因此,只有深⼊理解 Linux 下 TCP 内存的⽤途,才能正确地配置内存⼤⼩。
我们知道,TCP 必须保证每⼀个报⽂都能够到达对⽅,它采⽤的机制就是:报⽂发出后,必须收到接收⽅返回的 ACK 确认报⽂
(Acknowledge 确认的意思)。如果在⼀段时间内(称为 RTO ,retransmission timeout )没有收到,这个报⽂还得重新发送,直到收到ACK 为⽌。可见,TCP 报⽂发出去后,并不能⽴刻从内存中删除,因为重发时还需要⽤到它。由于 TCP 是由内核实现的,所以报⽂存放在内核缓冲区中,这也是⾼并发下 buff/cache 内存增加很多的原因。
接收⽅根据它的缓冲区,可以计算出后续能够接收多少字节的报⽂,这个数字叫做接收窗⼝。当内核接收到报⽂时,必须⽤缓冲区存放它们,这样剩余缓冲区空间变⼩,接收窗⼝也就变⼩了;当进程调⽤ read 函数后,数据被读⼊了⽤户空间,内核缓冲区就被清空,这意味着主机可以接收更多的报⽂,接收窗⼝就会变⼤。
TCP 报⽂头部中的窗⼝字段,就可以起到通知的作⽤。当发送⽅从报⽂中得到接收⽅的窗⼝⼤⼩时,
就明⽩了最多能发送多少字节的报⽂,这个数字被称为发送⽅的发送窗⼝。如果不考虑下⼀讲将要介绍的拥塞控制,发送⽅的发送窗⼝就是接收⽅的接收窗⼝(由于报⽂有传输时延,t1 时刻的接收窗⼝在 t2
时刻才能到达发送端,因此这两个窗⼝并不完全等价)。
从上图中可以看到,窗⼝字段只有 2 个字节,因此它最多能表达 216 即 65535 字节⼤⼩的窗⼝(之所以不是 65536,是因为窗⼝可以为0,此时叫做窗⼝关闭,上⼀讲提到的关闭连接时让 FIN 报⽂发不出去,以致于服务器的连接都处于 FIN_WAIT1 状态,就是通过窗⼝关闭技术实现的),这在 RTT 为 10ms 的⽹络中也只能到达 6MB/s 的最⼤速度,在当今的⾼速⽹络中显然并不够⽤。
RFC1323 定义了扩充窗⼝的⽅法,但 Linux 中打开这⼀功能,需要把 tcp_window_scaling 配置设为 1,此时窗⼝的最⼤值可以达到1GB (230)。
p_window_scaling = 1
这样看来,只要进程能及时地调⽤ read 函数读取数据,并且接收缓冲区配置得⾜够⼤,那么接收窗⼝就可以⽆限地放⼤,发送⽅也就⽆限地提升发送速度。很显然,这是不可能的,因为⽹络的传输能
⼒是有限的,当发送⽅依据发送窗⼝,发送超过⽹络处理能⼒的报⽂时,路由器会直接丢弃这些报⽂。因此,缓冲区的内存并不是越⼤越好。
缓冲区的调节范围是可以设置的。先来看发送缓冲区,它的范围通过 tcp_wmem 配置
p_wmem = 4096 16384 4194304
其中,第 1 个数值是动态范围的下限,第 3 个数值是动态范围的上限。⽽中间第 2 个数值,则是初始默认值。发送缓冲区完全根据需求⾃⾏调整。⽐如,⼀旦发送出的数据被确认,⽽且没有新的数据要发送,就可以把发送缓冲区的内存释放掉。⽽接收缓冲区的调整就要复杂⼀些,先来看设置接收缓冲区范围的 tcp_rmem :
p_rmem = 4096 87380 6291456
它的数值与 tcp_wmem 类似,第 1、3 个值是范围的下限和上限,第 2 个值是初始默认值。发送缓冲区⾃动调节的依据是待发送的数据,接收缓冲区由于只能被动地等待接收数据,它该如何⾃动调整呢?可以依据空闲系统内存的数量来调节接收窗⼝。如果系统的空闲内存很多,就可以把缓冲区增⼤⼀些,这样传给对⽅的接收窗⼝也会变⼤,因⽽对⽅的发送速度就会通过增加飞⾏报⽂来提升。反之,内存紧张时就会缩⼩缓冲区,这虽然会减慢速度,但可以保证更多的并发连接正常⼯作。发送缓
冲区的调节功能是⾃动开启的,⽽接收缓冲区则需要配置tcp_moderate_rcvbuf 为 1 来开启调节功能:
p_moderate_rcvbuf = 1
接收缓冲区调节时,怎么判断空闲内存的多少呢?这是通过 tcp_mem 配置完成的:p_mem = 88560 118080 177120tcp_mem 的3 个值,是 Linux 判断系统内存是否紧张的依据。当 TCP 内存⼩于第 1 个值时,不需要进⾏⾃动调节;在第 1 和第 2 个值之间时,内核开始调节接收缓冲区的⼤⼩;⼤于第 3 个值时,内核不再为 TCP 分配新内存,此时新连接是⽆法建⽴的。在⾼并发服务器中,为了兼顾⽹速与⼤量的并发连接,我们应当保证缓冲区的动态调整上限达到带宽时延积,⽽下限保持默认的 4K 不变即可。⽽对于内存紧张的服务⽽⾔,调低默认值是提⾼并发的有效⼿段。
同时,如果这是⽹络 IO 型服务器,那么,调⼤ tcp_mem 的上限可以让 TCP 连接使⽤更多的系统内存,这有利于提升并发能⼒。需要注意的是,tcp_wmem 和 tcp_rmem 的单位是字节,⽽ tcp_mem 的单位是页⾯⼤⼩。⽽且,千万不要在 socket 上直接设置 SO_SNDBUF 或者SO_RCVBUF ,这样会关闭缓冲区的动态调整功能。接收窗⼝
滑动窗⼝
# 拥塞控制
慢启动
让发送速度变慢是通过引⼊拥塞窗⼝(全称为 congestion window,缩写为 CWnd,类似地,接收窗⼝叫做 rwnd,发送窗⼝叫做 swnd)实现的,它⽤于避免⽹络出现拥塞。如果不考虑⽹络拥塞,发送窗⼝就等于对⽅的接收窗⼝,⽽考虑了⽹络拥塞后,发送窗⼝则应当是拥塞窗⼝与对⽅接收窗⼝的最⼩值。
可以根据⽹络状况和传输对象的⼤⼩,调整初始拥塞窗⼝的⼤⼩。调整前,先要清楚你的服务器现在的初始拥塞窗⼝是多⼤。你可以通过 ss 命令查看当前拥塞窗⼝:
ss -nli|fgrep cwnd
通过 ip route change 命令修改初始拥塞窗⼝:
ip route | while read r; do ip route change $r initcwnd 10
done当然,更⼤的初始拥塞窗⼝以及指数级的提速,连接很快就会遭遇⽹络拥塞,从⽽导致慢启动阶段的结束。
在规定时间内没有收到 ACK 报⽂,这说明报⽂丢失了,⽹络出现了严重的拥塞,必须先降低发送速度,再进⼊拥塞避免阶段。不同的拥塞控制算法降低速度的幅度并不相同,⽐如 CUBIC 算法会把拥塞窗⼝降为原先的 0.8 倍(也就是发送速度降到 0.8 倍)。此时,我们知道了多⼤的窗⼝会导致拥塞,因此可以把慢启动阈值设为发⽣拥塞前的窗⼝⼤⼩
拥塞避免
发送⽅已经达到了曾经发⽣⽹络拥塞的速度(拥塞窗⼝达到了慢启动阈值),接下来发⽣拥塞的概率很⾼,所以进⼊拥塞避免阶段,此时拥塞窗⼝不能再以指数⽅式增长,⽽是要以线性⽅式增长。接下来,拥塞窗⼝会以每个 RTT 增加 1 个 MSS 的⽅式,代替慢启动阶段每收到 1个 ACK 就增加 1 个 MSS 的⽅式。
快速重传
当连续收到 3 个重复 ACK 时,发送⽅便得到了⽹络发⽣拥塞的明确信号,通过重复 ACK 报⽂的序号,我们知道丢失了哪个报⽂,这样,不等待定时器的触发,⽴刻重发丢失的报⽂,可以让发送速度下降得慢⼀些,这就是快速重传算法。
快速恢复
knife是什么意思
每当接收⽅从⽹络中取出⼀个报⽂,发送⽅就可以增加⼀个报⽂。当发送⽅接收到重复 ACK 时,可以推断有失序报⽂离开了⽹络,到达了接收⽅的缓冲区,因此可以再多发送⼀个报⽂
修改拥塞控制算法
慢启动、拥塞避免、快速重传、快速恢复,共同构成了拥塞控制算法。Linux 上提供了更改拥塞控制算法的配置,你可以通过
tcp_available_congestion_control 配置查看内核⽀持的算法列表:
sysctl -a |grep tcp_available_congestion_control
再通过 tcp_congestion_control 配置选择⼀个具体的拥塞控制算法:
p_congestion_control = cubic
拥塞控制是控制⽹络流量的算法,主机间会互相影响,在⽣产环境更改之前必须经过完善的测试。
BBR 拥塞控制算法
当缓冲队列为空时,传输速度最快。⼀旦队列开始积压,每个报⽂的传输时间需要增加排队时间,⽹
速就变慢了。⽽当队列溢出时,才会出现丢包,基于丢包的拥塞控制算法在这个时间点进⼊拥塞避免阶段,显然太晚了。因为升⾼的⽹络时延降低了⽤户体验,⽽且从丢包到重发这段时间,带宽也会出现下降。
进⾏拥塞控制的最佳时间点,是缓冲队列刚出现积压的时刻,此时,⽹络时延会增⾼,但带宽维持不变,这两个数值的变化可以给出明确的拥塞信号,如下图所⽰:
这种以测量带宽、时延来确定拥塞的⽅法,在丢包率较⾼的⽹络中应⽤效果尤其好。2016 年 Google 推出的 BBR 算法(全称 Bottleneck