1. 从一张投诉工单说起:LTE MAC层的令牌桶到底在管什么
先说一个我在现场遇到过的真实场景。某个地市的用户报障,说家里用了一台LTE无线路由器,白天刷视频、打游戏都还正常,一到晚上家里人一起用就出问题:一台设备开着下载,其他设备的游戏延迟直接飙到几百毫秒,连微信语音都断断续续。后台抓了信令和MAC层的log,把上行调度的分配过程一条条摊开看,最后落在了一个很不起眼的地方——逻辑信道优先级过程里的令牌桶参数。
这就是LTE MAC层令牌桶算法要解决的事。它不是一个独立的功能模块,而是嵌入在MAC层逻辑信道优先级(Logical Channel Prioritization,LCP)过程中的一套速率控制机制。它的职责很明确:给每一条逻辑信道划一条"保底跑道"和一块"突发额度",让语音、信令这类小包业务永远不会被大流量业务饿死,同时又允许视频、下载这类业务在空闲时攒一点信用、集中发一发,不至于把资源卡得太死。
很多做外场测试的朋友会问,这不是基站调度器该管的事吗?其实要分两层看。调度器(eNB/gNB里的Scheduler)决定的是"这个TTI总共给你多少上行授权",而MAC层逻辑信道优先级里的令牌桶决定的是"这一笔授权在终端内部、在你自己的多条逻辑信道之间怎么分"。前者是外部资源,后者是内部经营。终端侧分不匀,外场测试的吞吐和时延数据一样会难看。
这篇内容适合谁看:做LTE/5G协议栈开发的、写基站调度算法的、研究终端Modem和CPE固件的,以及常年跑外场做上行优化和投诉定位的。涉及到的核心词汇是LTE、MAC层、令牌桶算法,我会从原理讲到参数计算,再落到外场测试和无线路由器场景里的坑,尽量让刚接触协议栈的人也能顺下来。
2. 令牌桶算法的底层逻辑:三个参数定生死
2.1 令牌产生速率PBR:每个逻辑信道的保底速率
在LTE的逻辑信道配置里,每一条逻辑信道会带一组参数,其中最核心的一个叫PBR,Prioritised Bit Rate,优先级比特速率,单位是kB/s。你可以把它理解成"这条逻辑信道每秒能领到多少令牌"。令牌是抽象的信用额度,攒够了才能发送数据,攒不够就得排队。
关键在于它叫"优先级位速率"而不是"保证位速率"。PBR只保证在资源不足的时候,每条逻辑信道至少能分到这么多,它不保证你在资源充足时被限制在这个速率。也就是说,PBR是一个下限保障,不是上限封顶。真正做上限封顶的,是基站侧调度器的PBR/GBR/MBR配合,以及APN级别的AMBR。终端内部的令牌桶,主要干的是"保底"这件事。
每个传输时间间隔(TTI,LTE里是1ms),逻辑信道的令牌变量Bj会增加PBR乘以TTI时长这么多令牌。1ms就是0.001秒,所以每TTI增加的令牌量是 PBR ÷ 1000,单位是kB。这个换算后面算参数的时候会反复用到,务必记清楚。
2.2 桶深BSD:决定了你能攒多少、突发多大
第二个参数是BSD,Bucket Size Duration,桶深持续时间,单位是ms。桶的最大容量等于PBR乘以BSD。翻译成人话就是:这条逻辑信道最多能攒多少令牌,取决于它每秒的保底速率和它被允许攒多久。
为什么要有桶深这个上限?如果令牌可以无限攒,一条低优先级逻辑信道在空闲很久之后突然爆发,就会一次性吃掉大量资源,把高优先级业务的时延搅乱。桶深就是给突发加了个天花板,让信用不能无限透支。
桶深的大小直接决定了令牌桶对突发的容忍度。桶深设小了,业务稍微突发一下就超桶,多余的令牌被丢弃,突发数据只能排队等下一个TTI,时延抖动变大;桶深设大了,突发容忍度高,但低优先级业务可能攒一大笔信用后集中爆发,挤压高优先级业务的空间。所以桶深是一个典型的"两头都不能过"的参数,得根据业务的突发特性和时延要求来定。
2.3 令牌桶和漏桶,LTE为什么选了前者
很多人会把这个机制和漏桶算法搞混,或者干脆以为就是一个东西。漏桶的逻辑是:不管你来多少数据,我都以恒定速率往外放,多余的要么丢要么缓存。它把流量整得极其平滑,但代价是不允许任何突发,一旦业务本身有突发特性,就会额外引入排队时延。
令牌桶不一样。空闲的时候令牌在桶里攒着,来一波突发数据,只要桶里有足够令牌,就可以一次性发出去,不用等。它允许突发,同时用桶深给突发设了上限,兼顾了平滑性和灵活性。
LTE MAC层为什么选令牌桶,我觉得根子在业务的突发性。视频编码有I帧P帧,TCP有慢启动和拥塞窗口增长,网页加载是一堆小包集中爆发,这些业务天生就不是匀速的。如果用漏桶强行整流,端到端时延和用户体验都会变差。令牌桶允许它们在一定范围内"抢一下",同时又靠PBR保住底线业务的速率,这个取舍是很务实的。
需要提醒的是,LTE这套机制管的是终端内部的逻辑信道分配,和我们在交换机上做的端口限速、和AP侧的带宽管理,虽然都叫令牌桶,但作用点和目的不完全一样,别混着套参数。
3. LTE MAC层里令牌桶的落点:逻辑信道优先级过程
3.1 每个逻辑信道都有一个Bj变量
逻辑信道优先级过程是MAC层每个TTI都要跑一遍的。为了让令牌桶落地,协议里给每条配置了PBR的逻辑信道维护一个变量Bj,中文一般叫"桶内令牌数"或者"当前信用额度",初始值是0,单位是字节。
Bj的更新有两步:每个TTI先往里加 PBR × TTI 的令牌(也就是前面说的PBR÷1000 kB),然后如果超过桶深上限,就把它截断到桶深值。这个过程不管你这一TTI有没有被调度、有没有发数据,都会执行。也就是说,一条长期不发数据的低优先级逻辑信道,Bj会一直攒,最多攒到桶深上限就不再涨了。
这里有个容易被忽略的细节:Bj的累加和截断是每个TTI都做的,和有没有授权无关。所以在外场测试里,如果一条逻辑信道长期占用不到资源,它的Bj会长时间顶在桶深上限,这本身不是故障,但它意味着一旦有资源,这条信道会优先把攒下的信用花掉,可能短时抢占高优先级业务的份额。这个现象在排查"高优先级业务偶发时延尖峰"的时候,是很有用的线索。
3.2 分配过程分两步:先保底,再按严格优先级吃掉剩余
整个分配过程可以概括成一个两阶段的流程。
第一阶段是按优先级从高到低遍历所有Bj大于0的逻辑信道,给每条分配资源,但分配量不超过它当前的Bj值。分完之后,从Bj里扣掉实际分配的量。这一步的意义就是兑现"保底速率"——只要桶里有信用,按优先级顺序满足你。
第二阶段是如果还有剩余的上行授权没用完,就按严格优先级顺序再遍历一遍所有逻辑信道,这次不管Bj是多少,直接按优先级把剩余资源分下去。这一步是"资源有富余就尽量用满",避免资源浪费。
用一段伪代码把逻辑理清楚,写协议栈的朋友可以直接对照实现:
// 每个TTI执行,grant 为本TTI的上行授权字节数 void lcp_process(int grant) { // 第一步:按优先级降序,优先满足 Bj > 0 的逻辑信道 for (lch in sort_by_priority_desc(logical_channels)) { if (lch.Bj > 0) { int alloc = min(lch.Bj, grant); assign_resource(lch, alloc); lch.Bj -= alloc; grant -= alloc; if (grant == 0) break; } } // 第二步:还有剩余授权,按严格优先级继续分配 if (grant > 0) { for (lch in sort_by_priority_desc(logical_channels)) { if (lch.has_data()) { int alloc = min(lch.pending_data, grant); assign_resource(lch, alloc); grant -= alloc; if (grant == 0) break; } } } // 第三步:更新每个逻辑信道的令牌,并做桶深截断 for (lch in logical_channels) { lch.Bj += lch.PBR / 1000; // 每TTI增加的令牌,单位kB if (lch.Bj > lch.bucket_size) { lch.Bj = lch.bucket_size; } } }注意:不同厂商的实现里,Bj的累加可能放在分配之前,也可能放在分配之后,只要保证每个TTI加一次、并且按桶深截断,逻辑上是一致的。对照实现时不要死抠顺序,要看它最终对时延和吞吐的影响。
3.3 令牌桶和BSR、调度器之间的配合关系
这里必须把三者的关系摆清楚,否则调参很容易调错地方。
BSR(Buffer Status Report)是终端告诉基站"我还有多少数据要发"。它上报的是逻辑信道组(LCG)里待发送数据的总量,是一个聚合值,不区分每条逻辑信道里各自有多少。
调度器拿到BSR之后,结合信道质量、公平性、QoS等一整套逻辑,决定这个TTI给这个终端多少上行授权,授权大小会随信道变化剧烈波动。
逻辑信道优先级过程,也就是令牌桶所在的地方,拿到的就是这个授权,然后在终端自己内部把这些资源分给各条逻辑信道。
所以分工是这样的:调度器管"总数",令牌桶管"内部分配",BSR管"上报需求"。你调令牌桶参数,影响的是内部怎么分,影响不了基站给你多少。如果外场发现是授权本身就不够,那再怎么调PBR也救不回来,得从调度器、覆盖、干扰这些方向找。这个边界感一定要有,不然现场会绕很大的弯。
4. 参数怎么算:PBR、BSD和实际速率的换算
4.1 单位换算与桶深计算,一步步来
令牌桶的参数计算,难点几乎全在单位上,我见过不少人在这里翻车。捋一遍:PBR单位是kB/s,TTI是1ms即0.001s,所以每个TTI往桶里加的令牌是 PBR 乘以 0.001,等于 PBR ÷ 1000,单位kB。桶深等于PBR乘以BSD,BSD单位是ms,换算成秒是除以1000,所以桶深的kB数等于 PBR × BSD ÷ 1000。
举个具体例子。假设一条VoLTE语音逻辑信道,QCI=1,PBR配置为48 kB/s,BSD配置为100 ms。桶深就是 48 × 100 ÷ 1000 等于 4.8 kB。每个TTI增加的令牌是 48 ÷ 1000 等于 0.048 kB,也就是约49字节。VoLTE一个语音包采用AMR-WB时,RTP载荷加各种头大约在几十到一百多字节量级,每20ms发一个。20ms是20个TTI,这段时间攒的令牌约是 0.048 × 20 等于 0.96 kB,约980字节,攒够一个语音包绰绰有余。桶深4.8 kB能扛住几个包的连续突发,这个配置对语音来说是够用的。
再看一个视频业务,QCI=2,假设PBR=512 kB/s,BSD=200 ms。桶深是512 × 200 ÷ 1000 等于102.4 kB。每TTI增加0.512 kB令牌,也就是约524字节。视频的I帧可能几百kB甚至上兆,桶深102.4 kB能扛一个中等大小的突发,但扛不住一个完整I帧,这意味着大I帧会被拆分到多个TTI发送,会引入一点时延但不会丢包,通常可以接受。
算这些参数的时候,我的经验是把"每TTI增加多少字节"这个数字算出来贴在配置表旁边,因为外场排查看log的时候,你一眼就能判断这个Bj增长速度合不合理,比盯着kB/s的配置值直观得多。
4.2 不同QCI的典型配置参考
下面这张表是我把常见厂商的默认配置和外场验证过的经验值整理出来的,具体项目还是要以运营商规范为准,这里给的是量级参考,不是唯一解。
| QCI | 业务类型 | 典型PBR (kB/s) | 典型BSD (ms) | 桶深 (kB) | 说明 |
|---|---|---|---|---|---|
| 1 | 会话语音VoLTE | 48~96 | 100~200 | 4.8~19.2 | 保底要够语音包节奏,桶深不用太大 |
| 2 | 会话视频 | 256~1024 | 200~500 | 51.2~512 | 桶深要能扛I帧突发 |
| 3 | 实时游戏 | 32~128 | 100~200 | 3.2~25.6 | 时延敏感,桶深宜小 |
| 4 | 缓冲流视频 | 128~512 | 300~500 | 38.4~256 | 允许较大突发 |
| 5 | IMS信令 | 16~64 | 100~500 | 1.6~32 | 优先级高,速率需求低 |
| 9 | 默认承载 | 1~8 | 300~500 | 0.3~4 | 低优先级兜底,防饿死即可 |
看这张表要抓住一个思路:PBR是"保底节奏",通常贴合业务的最小稳定速率需求;BSD是"突发容忍度",时延敏感业务BSD取小,吞吐型业务BSD取大。QCI=1这种语音业务,PBR只要够语音包的发送节奏就行,桶深也不用大,因为语音要的是准时,不是快。QCI=2的视频业务,PBR要覆盖平均码率,桶深要能缓冲I帧,这两者配合不好,就会在视频开头卡一下。
4.3 参数配置的几个硬性原则
第一个原则,高优先级业务PBR必须够用。所谓够用,是指它的PBR要能覆盖业务最小时延下的包到达速率。VoLTE每20ms一个包,那你PBR至少要能在20ms内攒够一个包。如果PBR配小了,Bj涨得慢,令牌攒不够,语音包就得在buffer里多等几个TTI,抖动和丢包就来了。
第二个原则,桶深不要盲目调大。桶深越大,低优先级业务能攒的信用越多,突然爆发时占用的资源越多,高优先级业务的时延尖峰就越明显。有的现场为了把吞吐数字做上去,把低优先级信道的桶深调得很大,结果就是下载一起来,语音质量就掉,得不偿失。
第三个原则,参数之间要联动看。PBR和BSD单独看没意义,桶深是两者乘积,真正影响突发行为的是桶深。调参的时候先定桶深目标,再围绕桶深去分PBR和BSD,思路会清楚很多。
5. 外场测试中的真实表现:三种典型症状
5.1 小区边缘上行受限,Bj长期顶在桶深上
外场测试里,小区边缘的场景最能把令牌桶的问题暴露出来。用户从中心往边缘走,SINR下降,MCS降级,基站给的上行授权越来越少。这个时候,终端内部的逻辑信道拿到的资源也跟着缩水。
我在路测里反复见过一个现象:在弱场区域,某些逻辑信道的Bj长时间顶在桶深上限不动,也就是伯令牌一直是满的。很多人第一反应是"桶坏了",其实恰恰相反,这说明这条信道一直攒令牌却花不出去,因为授权太少,根本轮不到它。这种情况下,即使你把PBR和桶深往上调,也不会有任何改善,因为瓶颈在无线侧的授权,不在终端内部。
判断方法很简单:把log里每个TTI的授权大小和Bj一起看。如果授权本身就很小甚至为零,Bj又长期满仓,那就是外部资源问题,往覆盖、干扰、调度策略上找;如果授权正常但Bj一直不动,才有可能是参数或逻辑问题。这个区分能帮你省下大量无效调参的时间。
5.2 VoLTE语音断续,PBR配小了
VoLTE断续是最典型的高优先级业务被饿死的例子。语音逻辑信道QCI=1,优先级最高,理论上应该最先被满足。但如果PBR配得偏小,Bj的token积累速度跟不上语音包的发送节奏,就会出问题。
具体怎么出问题?语音包每20ms到一次,你要在20ms内攒够一个包的令牌。如果PBR只有24 kB/s,每TTI加24字节,20个TTI攒480字节,遇到包稍微大一点、或者RTP头、PDCP头、RLC头占得多一点,就不够了。这时语音包就得等下一个20ms周期,累积起来,PDCP层的丢弃定时器一超,包就丢了,听感上就是断续。
这件事我印象很深,因为一开始大家怀疑是覆盖问题、是核心网问题,最后查出来就是PBR配小了。教训是:语音业务的PBR要按"最坏情况下包在20ms内能被攒够"来配,宁可留点余量,别抠那一点点数值。同时BSD也不用大,语音不需要攒那么多信用,桶深小一点反而能让令牌更快地"回收到Bucket里"以应对波动。
5.3 LTE无线路由器下的多终端争抢,令牌桶是最后一道闸
回到开头那个投诉。LTE无线路由器(CPE)这个场景很特殊:一台设备上挂着手机、平板、电视、电脑,所有终端的上行流量都要先汇聚到CPE的Modem里,再走LTE上行发出去。这个时候,CPE内部MAC层的逻辑信道分配,就成了家庭内部"谁先发"的最后一道闸。
家庭内网里,视频上传、网盘同步、游戏、语音,各占一条或几条逻辑信道。如果低优先级的大流量逻辑信道桶深配大了,它攒够了信用就能在某个TTI集中吃掉大量授权,把游戏和语音挤到后面几个TTI,表现就是游戏突然卡一下、语音突然糊一下。这类问题的定位,得把CPE侧的上行MAC log和设备侧的业务流量做时间对齐,看是不是某个业务爆发时正好对应了时延尖峰。
我的经验是,无线路由器这种多业务汇聚的场景,低优先级逻辑信道的桶深要克制,PBR保底够用即可,不要为了单个业务的峰值吞吐把桶深撑大。宁可让大流量业务慢一点、平一点,也要保证语音和游戏的时延稳定。这是家庭场景下用户体验的底线。
5.4 上行突发业务打不准节奏
还有一类不那么典型但挺烦人的场景:TCP慢启动或者短连接小包密集的业务,上行数据包一会儿一小串、一会儿又空一阵。令牌桶对这种节奏非常敏感。如果桶深太小,突发一来令牌就不够,多余的包只能排队。如果桶深够,突发能被平滑接住,用户体验就顺。
排查这类问题,不能只看平均值。要把短时间窗(比如100ms)内的包到达速率和Bj的变化对齐看,看突发那一刻桶里有没有足够令牌。很多时候平均速率看着完全正常,但瞬时突发没被接住,用户该卡还是卡。这也是为什么我一直强调,调令牌桶要看抖动和突发,不能只盯着平均吞吐。
6. 常见问题速查表与调参心得
6.1 问题与解决思路速查
| 现象 | 可能原因 | 排查方向 | 处理建议 |
|---|---|---|---|
| Bj长期顶在桶深不降 | 授权不足或该信道无数据 | 对比每TTI授权大小 | 属正常或外部资源问题,勿盲目调参 |
| 低优先级业务时延尖峰 | 桶深过大,突发抢占资源 | 看突发TTI的资源分配 | 调小BSD或桶深,限制其突发额度 |
| 高优先级业务随机丢包 | PBR不够,令牌攒不上 | 算每TTI令牌量对比包节奏 | 适当上调PBR,保证包节奏 |
| 弱场吞吐上不去 | 无线侧授权瓶颈 | 看MCS、SINR、授权大小 | 优化覆盖和调度,令牌桶无关 |
| 视频开头卡顿 | 桶深扛不住I帧 | 算I帧大小对比桶深 | 增大BSD增大桶深,容忍突发 |
| 语音抖动 | 优先级配置或PBR问题 | 看高优先级信道是否最先被满足 | 核对优先级映射和PBR |
| CPE下游戏卡顿 | 低优先级业务抢占上行 | 对齐业务爆发与MAC分配 | 收紧低优先级桶深和PBR |
| 上行吞吐不达预期 | PBR上限或调度联合限制 | 区分终端内部限制与外部授权 | 明确瓶颈在哪一层再动手 |
6.2 调参的顺序和一些踩坑经验
调令牌桶参数,我个人的顺序是先定优先级映射,再定PBR保底,最后定桶深。优先级映射错了,后面怎么调都别扭。确认每条逻辑信道的优先级和QCI对应关系是正确的,这是地基。
PBR的确定,以"最小时延下能攒够一个包"为底线。特别是语音和信令这类小包高频业务,底线算清楚,再往上留百分之二三十余量,基本就稳妥了。
桶深靠BSD来调,原则是时延敏感取小、吞吐型取大。定桶深之前先估算业务的典型突发大小,比如视频I帧大约多大,桶深至少要能覆盖这部分突发,否则大帧会被拆散,时延就会冒出来。
几个我踩过的坑也一并说说。第一,别照抄别家的参数。不同厂商默认优先级映射不一样,业务模型也不一样,抄过来的参数可能正好是错的方向。第二,改完参数一定要在外场复测,尤其要在弱场和多业务并发下复测,实验室向好不代表外场向好。第三,log采样率要够高,令牌桶是每TTI级别的动作,采样太粗会漏掉突发那一刻的关键信息。第四,怀疑令牌桶之前,先用半天时间确认不是调度器和覆盖的问题,这两个方向的概率其实比令牌桶参数配错更高。
6.3 关于外场测试的一点补充
做外场测试的时候,我习惯把上行方向的log按"授权—Bj—发送"三个维度做时序对齐。授权是基站给的,Bj是终端内部信用的,发送是实际动作。三者对齐之后,"资源不够""信用不够""分配失衡"这三种问题一眼就能分辨,比单看吞吐数字有用得多。尤其是LTE无线路由器这类汇聚设备,内部多业务互相影响,不做时序对齐根本看不出来是谁挤了谁。这个习惯帮我定位过很多次那种"数据看着都对、用户就是体验差"的疑难问题,也让令牌桶这块的调参从玄学变成了可以有序推进的排查流程。
我个人在实际操作中的体会是,令牌桶这套机制的设计意图很朴素——给每条业务留一条保底的道,同时留一点突发的余地,剩下的交给优先级。所有参数调来调去,其实都在回答同一个问题:这门业务,保底多少才够,能突发多大才不伤别人。把这个想明白了,参数就自然有了。