“CoDel直接观察出队等待,并判断高等待是否持续一段时间;本页观察的是到达时队长平均。相同q在不同链路速率下对应不同等待,因而不能把RED阈值包数直接当CoDel的时间阈值。”
同样排着五个包,在高速出口可能转眼清空,在低速出口却可能让交互请求等很久。CoDel给每个包记入队时间,在真正取出它时测量等了多久。它关注持续不消退的等待,同时保留吸收短突发的队列空间。
形式陈述
单队列的观察接口
在一个有硬字节容量C的FIFO上,每包有唯一ID、正字节长度、入队时刻。时钟单调,事件顺序明确,包长不超过给定M。到达若装不下便尾丢;成功入队才保留时间戳。尾丢不增加CoDel的控制count。[1,§5.4]
下游提供一次服务机会时调用dequeue,它至多返回一个实际待发送包,也可能先丢弃多个包。取出的候选p的驻留时间为now−p.tstamp。这里测到离开本FIFO为止;链路序列化、传播及下一队列时间不在这个数里。
参数TARGET>0、INTERVAL>0均用同一种整数tick。RFC8289的常见互联网设定是5ms和100ms;本例选择一tick=1ms来算状态,不把推荐值当作任意网络的定理。存储first_above_time(未设或某时刻)、dropping、count、lastcount、drop_next;初始未设、false、0、0、0。
每次取候选都重新判断
helper先从FIFO移除一包,并从总字节账本扣掉它,再执行:
- 没有候选:清first_above_time,返回“不宜丢”
- 驻留<TARGET,或移除候选后的剩余字节≤M:清first_above_time,返回“不宜丢”
- 否则,如果时间尚未设,置为now+INTERVAL,本次仍不宜丢
- 若早已设定且now≥first_above_time,才返回“宜丢”;未到时仍不宜丢
所以恰等于TARGET不触发低等待重置,剩余恰为M却触发保护。这是出队样本上的持续性判据,不是保存全部样本后求一个任意固定分桶的最小值,也不是靠一次高等待立即丢包。
两种控制状态
若原来dropping=true,先看当前候选;不宜丢就退出。否则,当now≥drop_next时,丢当前候选、count加1,再调用helper取下一候选。若它不宜丢则退出;仍宜丢就把drop_next从原计划时刻推进一个控制间隔,继续比较now。追赶循环不能写成一次if,因为本次服务机会可能来得很晚。
若原来dropping=false而候选宜丢,丢它并取下一候选,进入dropping。先算delta=旧count−旧lastcount;若delta>1且now−旧drop_next<16INTERVAL,使用count=delta,否则count=1。随后drop_next=now+控制间隔(count),lastcount=count。这是RFC§5.5的近期状态复用,不能每次进入都无条件归1。[1]
控制律的理想间隔为INTERVAL/√count。为使每个边界能用整数精确复算,附件明确取
这里isqrt返回非负整数平方根的向下取整。等式来自“最小正整数s使c·s²≥INTERVAL²”,没有浮点临界比较。向上取整是本教学实现的选择,并不声称与所有内核的定点近似逐tick一致;也避免很小间隔被截成0。
直觉
给端点时间响应,而不是连续清空队列
第一次观察到高等待,只启动观察期限。到期限后仍不低且还有足够余量,才开始通知;后续间隔逐渐缩短,让持续不响应的队列更频繁丢弃。若队列已回落,就及时退出,而不是把旧计划的所有丢弃额度当成必须花完的债。
剩余≤M时停止,是为了不把最后一包尺度的存量也反复削掉。它只约束此AQM动作;若下游调度器很久不给服务机会,CoDel不能凭空保证链路忙碌或包及时发送。
例子与边界
十包在0入队,服务机会有长间隔
令C=10000B、M=1000B、TARGET=5、INTERVAL=100。A..J各1000B同在0入队。另到一包因硬满被丢,count仍0。下游只在表列时刻提供服务机会,这份外部安排可以包括链路被其他任务占用;不是声称一个空闲且工作保持的出口会自行停到405。
| now | 先取的候选 | AQM动作 | 返回下游 | 控制状态 |
|---|---|---|---|---|
| 5 | A,等待5 | 设above=105;不丢 | A | dropping=false |
| 104 | B,等待104 | 尚未到105 | B | above仍105 |
| 105 | C | 丢C,再取D | D | count1,next205,lastcount1 |
| 405 | E | 追赶丢E/F/G/H | I | count5,退出dropping |
| 406 | J | 余量为0,不宜丢 | J | above未设 |
105刚丢C后,D仍有高等待,且移除D后还剩6000B。进入时count=1,计划205。到405才再调度,依次执行:
- 丢E后count2,下一计划为205+71=276
- 丢F后count3,下一计划为276+58=334
- 丢G后count4,下一计划为334+50=384
- 丢H后count5,再取I时只剩J的1000B,触发≤M保护,退出,不再推进计划
因此返回I,旧drop_next留384。若把每次计划写成now+s(c),第一步就变成405+71,漏掉该模型要求的后续追赶。若只按count计算一个批量丢数而不逐包重查余量,则可能把I/J一并删掉。
退出不意味着忘掉控制强度
接着在500重新入十包。505取第一个,设above605;605再次宜丢。此前count5、lastcount1、旧next384,delta=4,而且605−384=221<1600,所以重入count4、next655、lastcount4。
另从同一退出状态分支,直到2500才再来十包;2505设above2605,2605−384=2221≥1600,旧强度不再复用,count归1、next2705。两个分支分别说明近期复用和长期重置,不能连续套在同一状态上。
TARGET不是每包deadline
B已经等104,而TARGET仅5,它仍在开始丢弃前被交付。即使控制器工作正确,突发、长RTT和服务空窗都可能产生超过TARGET的个别包。这个反例就发生在主表中,无需诉诸实现异常。
把M误写成队列硬容量C,会使“剩余≤M”几乎永远成立,AQM无法进入;完全删掉余量检查又改变了低速链路上的行为。M是最大包尺度,C是存储预算,它们承担不同职责。
推论与应用
状态与终止性
每个helper只取出一个尚在队列的包;每次追赶循环必丢弃一个已取候选再取下一个。有限队列与同一调用中不穿插入队的合同保证循环结束。已经返回的包和丢弃的包都不再留在FIFO,字节账本始终等于其中实际包长之和。
以上可用归纳证明包不重复交付、占用不越容量和调用有限结束。它没有证明全部到达最终被交付,因为丢弃本来就是允许动作;也没有对不合作源给出公平吞吐保证。
RED的到达平均队长与这里的出队驻留时间是不同观测。改变链路速率时,同一队长的等待可显著变化;另一方面,服务机会暂时很少时,本页也只在实际取候选时推进控制,不是每个计划时刻都在后台强制删包。
成本与组合边界
有常数时间FIFO与运行字节计数时,一次调用丢k包再返回至多一包,控制工作O(1+k),空调用也有基础工作。整段运行每个入队包最多被helper取一次,所以不计整数平方根的算术位成本时,总队列工作为O(1+到达数+调用数)。不能从总摊还界推出每次调用最坏常数。
附件用大整数isqrt,时间随操作数位长变化;若固定机器字与定点近似,可另定单位成本模型。逐事件复制完整队列供审核还需O(1+队内包数)日志成本,未算进核心局部更新。
本页只实现单FIFO丢弃版。按流分队列需要另有流分类和调度;把丢弃换成CE标记时,标记包继续占位/交付,追赶循环和计数也应重新规定。不能只改drop函数而仍宣称其余状态自然等价。完整执行和两种重入见排队与反馈终点。
参考资料
- RFC8289: Controlled Delay Active Queue Management,2018,Experimental;§§3.1、4.1–4.5说明观测、包尺度与参数,§§5.2–5.6给状态、追赶、近期重入和控制律。本文改用未设值而非时间0哨兵,并明定整数向上取整;不冒称生产实现认证