ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

RED主动队列管理详解:从尾丢弃到随机早期检测

RED主动队列管理详解:从尾丢弃到随机早期检测

做网络运维这些年,我最怕看到的画面就是核心路由器出接口的队列深度一路顶满。TCP流量一多,丢包开始出现,所有连接像听到同一道号令一样集体降速,过一会儿又集体恢复,周而复始。懂行的人都知道,这里缺的不是带宽,而是能在队列完全塞满之前就主动干预的机制。这个机制的经典实现,就是RED(Random Early Detection,随机早期检测),很多老资料里也把这个算法简写为REA,业界更常见的叫法是RED。虽然现在CoDel、PIE这类新算法越来越流行,但RED依然是理解主动队列管理(AQM)绕不开的起点,也是大量设备、SDN交换机和Linux内核里仍在使用的默认能力。这篇文章围绕RED展开:为什么要主动丢包、算法本身怎么工作、拿到设备上该怎么配、怎么调、怎么排障。

1. 为什么网络需要RED:传统尾丢弃的三大痛点

1.1 收费站视角看尾丢弃:问题都在"最后一刻"

先说说传统队列最常用的处理方式——尾丢弃(Tail Drop)。所谓尾丢弃,就是数据包进入设备后先在缓存队列里排队,队列满了以后,新到的包直接扔掉,不管这个包是语音、视频还是网页请求。

你可以把它想象成高速公路收费站的入口:车道的排队空间就那么大,队尾排满了之后,后面来的车不会得到任何提示,直接被引导离开。真正的问题在于,这种机制在"最后一刻"之前没有任何反馈。队列从0涨到满的整个过程中,系统完全不干预,直到溢出的那一刻,后续所有流量一起遭殃。

对于TCP来说,丢包是唯一的拥塞信号。尾丢弃等于在所有流已经挤到极限的时候才给出信号,而且一给就是"全员通知"。这个模式在流量小的时候没什么问题,一旦流量大起来,队列长期处于满水位,各种问题就会接二连三地出现。

1.2 全局同步:TCP流量的集体抽搐

这里要说一个尾丢弃最典型的并发症——全局同步(Global Synchronization)。假设有几条TCP流共享同一个瓶颈队列,各自都在稳定地增长发送窗口。队列慢慢变满,直到某几个包同时被尾丢弃。问题来了:这些流几乎在同一时刻感知到丢包,于是所有流都执行拥塞避免算法,把窗口减半甚至触发超时重传。

结果就是链路利用率像锯齿一样上下抖动:前面几秒链路满负荷,丢包后一瞬间链路空转,带宽白白浪费;流恢复增长之后又一起涨到满,再次集体丢包。你在现网里看到的表象就是"带宽明明还有富余,用户却一阵一阵卡"。

RED的巧妙之处,就是不等到队列满,而是让每个包被丢弃的概率和平均队列长度挂钩。队列越长,丢弃概率越大,至于具体丢到谁头上,则是随机点名。这样一来,一次拥塞事件里只有一部分流被"点名"降速,系统整体不用集体急刹车,链路利用率就能稳在一个比较平滑的水平。

1.3 Bufferbloat:队列越长,时延越高

还有一类问题比丢包更隐蔽,就是排队时延膨胀。很多设备的缓存其实不小,动辄几百KB甚至数MB,本意是吸收突发流量。但如果没有主动队列管理,TCP流会让队列长期维持在一个很高的水位。每个包进来都要先排队,RTT从正常的几毫秒被拉到几十甚至几百毫秒。

这个现象有个专门名字叫Bufferbloat,中文常译作"缓冲膨胀"。表现在用户体验上,就是视频打开慢、网页卡顿、游戏操作迟滞,但链路利用率看起来又很高。RED提前丢包,本质上是让TCP提前感知拥塞并降速,队列自然就维持在一个低而稳定的水位,排队时延随之降下来。这就是"主动"两个字的意义:不等溢出,提前通知。

2. RED核心机制拆解:平均队列长度与三段落丢弃

2.1 平均队列长度:给瞬时队列装一个阻尼器

RED最核心的一个设计,是它不使用瞬时队列长度来做判断。网络流量天生是突发的,如果拿瞬时队列长度当触发器,一次毫秒级的突发就可能触发一串丢弃,而这些包本来队列稍微缓冲一下就能容纳,丢了完全是浪费。

RED维护的是一个指数加权移动平均(EWMA),公式写成这样:avg = (1 - w_q) × avg + w_q × curq。其中curq是当前瞬时队列长度,w_q是权重系数。你可以把它理解成一个带阻尼的体温计:测的不是每秒都在跳动的体温读数,而是一段时间内平滑变化后的趋势。w_q越小,平均值对瞬时变化越迟钝,过滤突发的能力越强;w_q越大,平均值越灵敏,但误丢的可能性也越高。

为什么这个设计重要?因为RED的丢包判断必须"慢半拍"。它的目标不是惩罚一次突发,而是识别持续累积的拥塞趋势。没有这层平均化,RED和尾丢弃在效果上就拉不开差距。

2.2 三段落策略:min_th、max_th与max_p各自管什么

RED把平均队列长度分成三个工作区域,对应三种处理策略:

区域平均队列长度条件丢包行为设计意图
绿色区avg < min_th全部转发容忍正常突发,不干预
黄色区min_th ≤ avg < max_th按概率随机丢包,概率从0线性涨到max_p让部分流先降速,平滑释放压力
红色区avg ≥ max_th全部丢弃兜底保护,防止队列溢出失控

黄色区是RED的灵魂所在。当前平均队列超过min_th后,丢包概率按线性关系增长:p_b = max_p × (avg - min_th) / (max_th - min_th)。等avg到了max_th,概率就涨到max_p,超过之后直接全部丢。之所以用线性渐变而不是阶跃,是为了给TCP一个渐进的拥塞信号,让各条流可以分批次降速,而不是再次走向全局同步。

关于参数搭配,最初那篇论文里的建议至今仍然适用:max_th至少取min_th的2到3倍,min_th设置到能容纳典型突发的大小。两个阈值靠得太近,黄色区太窄,随机丢弃和全丢弃会频繁切换,反而制造抖动。

2.3 w_q、max_p怎么定:给一套能落地的调参思路

参数敏感是RED最让人头疼的地方,但也不是无迹可寻。我的做法是先定目标时延,再倒推阈值。

假设链路带宽是10Mbps,平均包长1000字节,我希望把排队时延控制在100毫秒以内。目标时延对应的队列水位大约是带宽乘时延:10Mbps × 0.1秒 = 100万bit,约等于125KB,折算下来大概83个1500字节的包。那么min_th取这个量级就合理,max_th按2到3倍关系取,也就是250KB到400KB附近。如果你把目标时延定成10毫秒,算出来只有12.5KB,约等于8个1500字节包,这个水位太浅,稍微有点突发就会触发丢弃,实际部署很难稳定。

max_p一般从0.01到0.1之间选。设得太高,随机丢弃逼近全丢弃,对TCP吞吐伤害明显;设得太低,拥塞信号太弱,队列照样淤积。我自己的经验是从0.02起步,观察平均队列曲线稳定后再微调。w_q则建议维持经典值0.002附近,千兆以上高带宽链路可以再取小一点,让平均值更稳定。Linux的tc工具里不直接暴露w_q,而是通过burst参数按公式换算,后面实操部分会再说到。

3. 从RED到WRED、ARED与ECN:把算法用得更顺手

3.1 WRED:按优先级实施差异化丢弃

基础RED对所有流量一视同仁,但现实网络里不行。语音、视频会议、关键业务系统,和后台下载、日志同步,不该享受同样的丢包待遇。于是就有了加权随机早期检测,WRED(Weighted RED)。

WRED的做法是为不同的DSCP值或IP优先级准备独立的丢弃参数,每种流量拥有各自的min_th、max_th和max_p。高优先级流量的阈值设得高、丢弃概率设得低,意思是"队列紧张时先丢你们的机会不大";低优先级流量阈值低、丢得早,意思是"拥塞时先拿你开刀"。

在主流厂商路由器的命令行里,WRED的典型配置长这样(以DSCP-based模式为例):

policy-map WAN-EDGE class EF bandwidth percent 10 random-detect dscp 46 100 200 10 class AF41 bandwidth percent 30 random-detect dscp 34 80 160 10 class class-default random-detect dscp 0 40 80 10 ! interface GigabitEthernet0/0 service-policy output WAN-EDGE

语法里random-detect后面四个值分别是DSCP值、最小阈值、最大阈值和标记概率分母。分母为10时,最大丢包概率就是1/10。这里我踩过一个坑:给语音EF队列也配上很小的阈值,结果一拥塞语音反而先丢,完全背离了初衷。我的建议是,EF这类高优先级队列要么不配RED,要么把阈值设到极大,只在极端拥塞时起保护作用,真正需要精细化丢弃的是AF和BE这类有损容忍的流量。

3.2 ARED:参数自动调节,省去手动摸索

RED参数敏感,人工调优又费时,于是有了自适应RED,简称ARED。它的思路是周期性观察平均队列长度和目标区间的关系:平均队列老是偏高,就自动加大max_p,让丢弃更激进一点;平均队列长期偏低,就自动减小max_p,减少误伤。

这套机制对现网环境特别友好,尤其是流量模式变化大的场景。白天高峰期流的数量可能是夜里的几十倍,人工维护一组固定参数根本跟不上。在Linux的tc里,给red队列加上adaptive关键字就能启用ARED,实测下来比固定参数省心不少。我现在的原则是:能用ARED的地方尽量用,只有需要非常细粒度控制时才退回手工参数。

3.3 ECN:从"丢包通知"升级为"标记通知"

RED的另一个重要增强是ECN(Explicit Congestion Notification,显式拥塞通知)。传统的拥塞通知就是丢包,而ECN允许路由器在拥塞时不动数据,只在IP头里打一个拥塞标记。接收端看到标记后,通过ACK里的ECE标志把拥塞信息回声给发送端,发送端照样降窗,但数据包本身完好无损,不需要重传。

要启用ECN,链路两端都要配合。在Linux上发送端和接收端需要把net.ipv4.tcp_ecn设为1,队列侧的tc命令里要带上ecn关键字。我见过最常见的翻车现场,就是只改队列侧没改主机侧,结果查了半天一个标记包都没有。另外,ECN标记只对支持ECN的流量有效,不支持的老流量仍然走正常丢弃路径,所以两者是并行关系,不会互相干扰。

对延迟敏感的应用,比如网页首屏和实时交互,ECN的价值特别大。丢包意味着至少一个RTT的重传等待,而ECN标记是同一条数据路径上即时传递的,能少一次往返。

4. 实操示例:在Linux中搭建一个RED验证环境

4.1 实验拓扑与初始化准备

纸上谈兵没意思,下面给一套可以直接复现的实验方案。拓扑三台Linux主机:A作为发送端,B作为接收端,中间一台双网卡主机R做转发,把瓶颈设在R的eth1出方向。

三个节点各自配置好IP:A用192.0.2.10/24,R的eth0用192.0.2.1/24,eth1用198.51.100.1/24,B用198.51.100.10/24。确保A到B的流量经过R转发,并且R的IP转发已开启。注意关掉R上不必要的加速特性,避免硬件卸载干扰队列观察,纯软件环境测试结果更干净。

4.2 tc命令配置RED的完整过程

关键一步是先在eth1上制造一个明确的瓶颈。物理链路是100Mbps,我想模拟10Mbps的窄带出口,先用htb把速率限住,再在限速class下面挂red队列:

# 创建根句柄1:,默认class 10 tc qdisc add dev eth1 root handle 1: htb default 10 tc class add dev eth1 parent 1: classid 1:1 htb rate 10mbit ceil 10mbit tc class add dev eth1 parent 1:1 classid 1:10 htb rate 10mbit ceil 10mbit # 在class 10下挂red队列,开启ECN tc qdisc add dev eth1 parent 1:10 handle 10: red limit 600kb min 60kb max 240kb avpkt 1000 burst 50 probability 0.02 ecn bandwidth 10mbit

逐个解释参数:limit是队列物理缓存上限600KB;min和max对应min_th和max_th,60KB和240KB;avpkt是平均包长估计值,按1000字节算;burst是突发容忍量,tc用它反推w_q,典型算法里burst越大w_q越小;probability就是max_p,0.02;bandwidth告诉内核所在链路的速率,用于换算参数;ecn开启标记模式。

这里有个新手常犯的错误:直接把red挂成root qdisc。如果接口速率本身远大于流量速率,队列永远不满,RED压根不会介入,看起来"配了等于没配"。必须先限速或确实存在物理瓶颈,让队列有积压的空间,RED才有用武之地。

配置完成后可以用下面的命令观察统计:

tc -s qdisc show dev eth1

输出里会显示backlog积压量、dropped数量,以及开启ecn后的marked标记数量。这几个数值是后面判断配置是否生效的重要依据。

4.3 iperf3压测与结果解读

打流工具我用iperf3。接收端启动服务:

iperf3 -s

发送端开10条并发TCP流,持续120秒:

iperf3 -c 198.51.100.10 -t 120 -P 10

建议分三组对比。第一组只做htb限速不加red,相当于纯尾丢弃基线;第二组在htb下挂red但不带ecn;第三组red加ecn。重点看三个指标:总吞吐是否平稳、tc统计里dropped的时间分布是否均匀、以及ping测出来的RTT是否稳定。

我实际跑下来的典型结果是这样:纯尾丢弃那组,iperf3每秒输出的吞吐数据锯齿明显,偶尔出现整条流跌到接近0的尖峰,ping的RTT在几毫秒到上百毫秒之间剧烈跳动;加了red之后,吞吐整体接近限速值但波动幅度明显收窄,RTT的抖动也小了一个量级;再加上ecn后,统计里marked逐渐增加而dropped明显减少,吞吐曲线更平滑。这个现象背后就是RED的随机早期丢弃让各条流错峰降速,避免了全局同步。

如果要用UDP流量做对照,可以这样打:

iperf3 -c 198.51.100.10 -u -b 20M -t 120

UDP没有拥塞窗口,RED丢再多它也不降速。跑完之后你会看到dropped数量很多但UDP发送速率纹丝不动。这个实验能直观说明一个道理:RED对TCP流有效,对UDP流只能做到"丢该丢的",没法让UDP源自动收敛。

5. 常见问题与排查技巧实录

5.1 配置了RED吞吐反而上不去的三个原因

这是我收到过最多的反馈,配置完RED之后链路吞吐不升反降。排查优先级从高到低排序:

第一,min_th和max_th设得太小。平均队列长度几乎永远停在黄色区,随机丢包连续不断,每条流都以为网络很堵,都不敢开大窗口。处理办法是按第2节的时延倒推法重算阈值,把黄色区放宽。

第二,max_p设得太大。我见过有人直接抄别人配置把probability设成0.5,这等于大部分包都在丢。从0.01到0.02起步,观察队列曲线后再收紧。

第三,burst和w_q不匹配。burst设得太小导致平均队列对瞬时队列过于敏感,一有突发就误丢一批。检查tc配置里的burst值,按man page里的公式重新计算。

我的排障顺序永远是先看tc -s qdisc show里的dropped速率:如果丢包持续均匀存在,多半是max_p问题;如果丢包集中在压力瞬间,多半是阈值或burst问题。

5.2 UDP流量不受控,RED被"打穿"

RED对付不了UDP的持续高压。原因很简单,UDP没有拥塞窗口,RED丢它的包,它转眼又原速灌进来。如果在一条混有大量UDP的链路上只做普通RED,最终效果是UDP流量抢占了队列,TCP流量被挤到黄色区里持续丢包,体验更差。

网上的解决办法,还是得靠流量分级。语音、视频这类UDP流量放进高优先级队列,配大阈值,尽量少丢;后台下载等低优先级流量放默认队列,配小阈值,拥塞时先丢它们。这就是WRED存在的意义——不是把RED做得更复杂,而是让不同流承担不同的拥塞代价。

5.3 ECN开启后却没有标记包

出现这个现象时,我会按下面三个环节逐一查:队列侧的命令有没有带ecn关键字;发送端和接收端的net.ipv4.tcp_ecn是不是1;中间设备有没有把ECN字段当成普通保留位处理。其中主机侧最容易漏,因为很多发行版默认值是0或者2,2的意思是被动响应但不主动请求,效果也和1不一样。

还要注意一个细节:Linux的tc red里,ecn标记只对平均队列位于min与max之间的包生效,一旦avg超过max就转为直接丢弃。所以如果队列已经长期处于红色区,你看到的只会是dropped猛涨而marked很少,这其实是阈值设置问题,不是ECN没生效。

5.4 一张表看清常见误配置

症状可能原因处理动作
平均队列持续顶到limitmin/max设太高,TCP来不及在溢出前降速按目标时延重算阈值,适当下调
整体吞吐偏低max_p过大,丢包过激从0.01到0.02起步调小
突发流量一来就误丢w_q过大或burst过小增大burst,降低w_q
高速链路完全看不出效果参数没有按链路带宽重算以带宽时延积为基准重新估算
ECN标记数为0主机侧tcp_ecn未开启或队列未加ecn关键字两端检查sysctl与tc配置
UDP与TCP混跑时TCP被饿死未做流量分级,默认队列共用配置WRED或优先级队列隔离

6. 从RED到现代AQM:我的选型建议与调参心得

6.1 在现网调RED的三条体会

第一条,参数不能抄作业。同一组min_th和max_p,换一条链路就完全是另一个效果。链路带宽、平均包长、目标时延、并发流数量,每一样都会影响最优参数。老老实实按自己的数据算一遍,比直接抄任何现成配置都靠谱。

第二条,优先打开ARED和ECN。这两个特性一个解决参数自适应,一个缓解丢包对上层应用的影响,都是在不大幅增加配置复杂度的前提下提升体验的方案。我在几个流量波动很大的出口链路上做过对比,固定参数RED要隔三差五调一次,ARED基本托管。

第三条,观察平均队列曲线比看接口利用率有价值得多。接口利用率只告诉你"忙不忙",平均队列曲线告诉你"堵没堵、堵了多久"。把tc统计接入监控,每天看avg曲线的形态,往往能提前几天发现拥塞趋势。

6.2 RED之后的演进方向

RED之后还有CoDel、PIE、FQ-CoDel这些更现代的主动队列管理算法,它们在Linux里开箱即用,理念上都是从"提前干预"这个思路长出来的。区别主要在于不再依赖手动调参:CoDel用最小排队时延作为拥塞指标,PIE用排队时延变化率作反馈,FQ-CoDel更是把公平队列和AQM结合到了一起。

但如果要理解它们为什么这么设计,回到RED把算法原理彻底吃透是值得的。我后来排查过一次诡异的瞬时丢包问题,最后定位到就是对窗口内的数据包做了资源预分配,本质上和RED面向拥塞提前介入的思路一脉相承。学到后面的算法,再回头看这些老设计,你会更明白设计者当时做出的每一个取舍。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进