恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Max-Min公平性详解:从水填充算法到多用户带宽分配实践
首页
资讯中心
/
Max-Min公平性详解:从水填充算法到多用户带宽分配实践
Max-Min公平性详解:从水填充算法到多用户带宽分配实践
发布时间:2026/10/14 23:24:47
1. 资源分配里的兜底哲学做过多用户资源分配的工程师大概都经历过这种争论系统里有几个大户疯狂占用带宽也有几个小户的需求一直得不到保障。老板拍板说要公平可什么是公平按人头平分按权重分配还是优先喂饱贡献最大的那批用户几年前我做某跨平台系统的流量调度模块时遇到的就是这个难题。在一次需求评审会上某运营商客户明确提出一句话我要保证最差的那一路流也不能被饿死。这句话听起来朴素翻译成技术语言其实就是一个经典模型——Max-Min公平性Max-Min Fairness。它是网络资源分配、计算任务调度、存储带宽控制等场景里保障底线的核心算法思想基本思路是在满足所有参与者最小需求的前提下让资源分配结果中最小的那个份额尽可能大然后再处理次小、依次类推。这个模型适合谁在我看来任何一个做过多用户共享资源的工程师都值得花时间吃透它。不管你做的是路由器QoS队列、基站无线资源调度、分布式存储限流还是API网关的多租户配额只要资源是共享的、用户是异质的、需求是动态的Max-Min公平性就能帮你避免系统崩溃时先把弱势方挤掉的糟糕结局。2. 为什么最大化最小值是公平的底线标准2.1 从分蛋糕说起想象一个场景一块蛋糕分给三个人。第一个人食量特别大第二个人正常第三个人刚吃完午饭。如果按自由竞争来第一个人能吃掉八成如果按绝对平均来三个人各吃三分之一但第三个人可能根本吃不完浪费了。Max-Min思想给出的答案是另一套逻辑先想办法让吃得最少的那个人尽可能多吃一点达到这个目标之后再看第二少的人能怎么提升直到某种约束条件比如总资源上限卡住为止。它不追求总量的最大化也不追求某个高优先级用户的最大化而是优先保障最低位的体验。这套逻辑在通信系统里特别实用。举个例子一个小区的基站带宽总量有限有的用户在看4K视频有的用户在刷网页有的用户在做视频通话。视频通话这种实时业务对带宽下限极敏感一旦速率掉到某个阈值以下就是灾难网页浏览的弹性很大多给点少给点都能用。Max-Min公平分配的直观结果就是先把每个用户保底满足到再也无法同时继续提升最小值的程度多余的资源再按约束分配出去。这样最弱势的用户体验被托住了整体也不至于浪费资源。2.2 数学上的严谨表达用公式说话。假设有 n 个用户每个用户分配到的资源量为 x_i每个用户都有一个需求上限 r_i比如用户当前请求的带宽、任务需要的计算量。链路总容量为 C。Max-Min公平分配对应的优化目标可以写成最大化 min(x_1, x_2, ..., x_n) 约束 sum(x_i) C 0 x_i r_i但最大化最小值只是第一目标实际计算时往往分层次进行先最大化最小的 x_i再在满足这个条件的前提下最大化第二小的 x_i再依次类推直到分配向量无法在不减少某个更小份额的前提下增加任意一个更大的份额。这种递归式的优化目标才是完整的Max-Min公平定义。2.3 分配结果到底长什么样假设总容量 C100 Mbps有四个用户需求上限分别是 r[10, 30, 50, 100]。用Max-Min的思路推演一遍先把资源想像成水位从0开始上涨四个用户同时获得等量的资源。水位涨到10时用户1的需求满了不再参与后续分配。继续涨剩余三个用户继续平分资源。当水位到30时用户2也满了退出。剩下用户3和用户4继续平分剩余容量。最终结果大约是 x[10, 30, 30, 30]总和100。直观结论是需求小的用户先被满足需求大的用户共享剩余资源最终所有未能被完全满足的用户拿到相同份额。这和水往低处流、洼地被逐级填平的物理过程高度类似这就是水填充算法Water-Filling这个叫法的来历。3. 和其他分配策略的对比各有各的账很多初学者会把公平简单理解成平均分或按比例分但在工程实践中这几种策略各有适用面。我做了个对比方便读者按场景选型。策略核心目标分配结果特征典型场景主要短板平均分配总额/用户数所有人相同忽略需求差异测试环境、演示Demo需求小的浪费需求大的饥饿加权比例分配sum(x_i)C按权重 w_i 分配有优先级的用户拿更多企业级限流、多租户配额权重低的用户可能被挤压至零最大吞吐量最大化 sum(log(x_i))兼顾公平与效率中等用户受限小用户保底无线通信调度、HTTP自适应流媒体对小用户不够友好Max-Min公平最大化最小份额再依次最大化次小需求小的先满足未饱和用户等量QoS队列、资源兜底、多业务共存总吞吐量不是最高实现略复杂这里有个特别容易踩的认知误区Max-Min不是要牺牲大户来补贴小户而是先托底再分配。它在数学上保证了很多场景下比加权比例分配更能防止某些用户被饿死但代价是系统的总吞吐量往往不是最优的。如果你做的系统核心KPI是总带宽利用率那大可以把Max-Min和其他效率优先的策略做加权组合如果核心KPI是最差用户不能掉线Max-Min就是首选。个人经验是不要一开始就把全部流量调度逻辑押在单一策略上。我见过不少悲剧团队一上来就追求极致公平结果大户被削得太狠付费用户投诉体验掉得厉害。后来改为分层策略——先给实时业务一个Max-Min保底配额再用加权比例分配弹性流量整体就顺了。4. 从原理到实现水填充算法实操4.1 算法步骤Max-Min公平性的经典实现就是水填充算法。如果你想在自己的系统里实现它一套可靠的做法是这样的输入准备收集所有用户当前的需求上限 r_i 和系统总容量 C。如果某个用户的实际需求小于分配到的资源它就会退出不再参与后续分水。初始化所有用户的分配额 x_i 0当前剩余容量 C_rem C活跃用户集合 A {所有用户}。迭代填水在每一轮中把剩余容量 C_rem 平均分给活跃用户集合 A 中的每个人每人临时增加 C_rem / |A|找到活跃用户里最小需求上限对应的用户 j。判断收敛如果分配未超过用户 j 的需求上限这轮结束所有活跃用户都拿到相同增量算法终止如果超过则把用户 j 的需求补满即 x_j r_j把用户 j 移出活跃集合更新 C_rem回到第3步继续下一轮。输出结果活跃集为空或剩余容量为0时所有用户的分配额即为最终结果。4.2 Python参考实现为了能直接抄作业我给出一个简化的Python实现。这个版本适合网络带宽分配和任务配额计算的建模def max_min_fair_allocation(capacity, demands): 计算Max-Min公平分配向量 参数: capacity: float, 系统总容量 demands: list[float], 每个用户的需求上限 返回: allocation: list[float], 每个用户最终分配量 if capacity 0: return [0.0 for _ in demands] n len(demands) active set(range(n)) # 活跃用户集合 allocation [0.0 for _ in demands] remaining_capacity float(capacity) while active and remaining_capacity 1e-9: cnt len(active) # 本轮平分把剩余容量均摊给所有活跃用户 share_each remaining_capacity / cnt # 找到活跃用户中需求上限最小的用户 min_demand float(inf) min_idx None for i in active: # 该用户还缺多少额度取需求上限与当前均摊额的较小者作比较 required demands[i] - allocation[i] if required min_demand: min_demand required min_idx i # 如果最小的需求缺口都比均摊额大说明给所有活跃用户再加share_each # 也不会让任何一个人超出需求那么整体填满后可以结束 if min_demand share_each: for i in active: allocation[i] share_each remaining_capacity 0.0 else: # 超出最小需求用户的上限按该用户的缺口分配 for i in active: allocation[i] min_demand remaining_capacity - min_demand * cnt # 将已达需求的用户移出活跃集合 active.remove(min_idx) return [round(x, 6) for x in allocation] if __name__ __main__: cap 100 demands [10, 30, 50, 100] result max_min_fair_allocation(cap, demands) print(分配结果:, result) # 预期 [10, 30, 30, 30] print(总和:, sum(result))这段代码的核心逻辑就是前述迭代填水的实现。active集合维护尚未满足需求的用户min_demand是当前最小缺口如果最小缺口比均摊额大说明所有活跃用户都能再均匀涨一截而不超需求直接终局否则先把最小缺口用户补满踢出集合再继续下一轮。这种时间复杂度是 O(n^2)因为每轮都有一个找最小值的循环。如果你的用户规模到几千甚至上万比如网络流量调度里的流数量建议用最小堆或优先队列优化到 O(n log n)。实际业务中系统每秒钟都在做调度决策性能优化不是可选项而是必选项。4.3 有权重约束的变体现实情况往往不是所有用户生而平等——VIP客户、付费订单、实时语音流的优先级天然更高。这时候标准的Max-Min分配不足以表达需求需要引入带权重的Max-Min公平性让每个用户分配到的资源量 x_i 与权重 w_i 的比值公平也就是让 min(x_i / w_i) 最大化。实现上的差异很小把平分剩余容量改成按权重比例分配剩余容量。更准确地说是让所有活跃用户的 x_i / w_i 尽量相等。在Python实现里share_each需要从固定值变成按权重动态分配判定的最小需求缺口也以 x_i/w_i 为基准。这个变体在商用QoS设备里非常常见值得重点掌握。还要提醒一点权重怎么定义是个业务问题不是算法问题。是把带宽付费金额作为权重还是把业务类型映射成权重直接影响最终体验。我踩过的一个坑是权重定得太大导致某些低权重用户长期分配不到资源被运维叫去优化问题。后来改成权重经过对数压缩再用Max-Min约束局面立刻改善——权重差距不宜放飞自我否则保底公平的初衷也就消失了。5. 实操案例多用户带宽分配的一次完整复现5.1 项目背景某公司在做一个视频加速网关要同时服务三种业务流实时视频通话、直播流、普通网页下载。网关的上联带宽是1 Gbps三个业务的典型需求如下实时视频通话单路带宽需求2 Mbps非常敏感给低于1.5 Mbps就会卡顿时但通常不会超过3 Mbps。直播流弹性较大单路需求5 Mbps起步但可以到20 Mbps。网页下载完全弹性有多少用多少但不能占满全部带宽。问题在于如果只按Priority处理网页下载可能直接被压到0导致HTTP请求超时。如果按比例分配视频通话可能拿到的带宽超出需求造成浪费。Max-Min的思路则能保证三者都有合意的下限。5.2 建模与配置这个场景可以抽象成三个用户参与Max-Min分配需求上限分别是 [3, 20, 200]数字仅示意总容量C1000。前面Python代码直接运行就能得到大致结果现实工程里还会有更复杂的每路流粒度。实际我用的方案是分层限流第一层识别业务类型深度包检测或端口匹配把流量分到三个队列。第二层为每个队列设定Max-Min权重和需求上限。实时通话队列权重设最高网页下载权重设最低但下限值即最小保证额度通过Max-Min计算得到。第三层将Max-Min计算出来的额度下发到Linux tc或硬件QoS模块每队列按额度限速。5.3 观测结果跑了一段时间的数据最明显的变化是视频通话的最差时延从平均350ms降到了80ms左右网页下载的最差完成时间缩短了将近60%。直播流因为弹性太大分配到的带宽略低于原来抢资源时期的峰值但直播本身有缓冲机制体验并没有劣化。事后复盘时我特别重视一个细节Max-Min不是一锤子买卖算法要周期性重新计算。用户的需求上限和活跃集合是动态变化的比如用户打开了一个新网页下载任务需求上限从0跳到200另一个用户挂断了视频通话需求上限从3掉到0。系统每100ms跑一次水填充把最新额度下发到转发引擎才能保证公平性持续成立。5.4 一个容易忽视的小坑需求上限怎么定很多团队把需求上限拍脑袋设成一个很大的数比如把网口速率当上限结果Max-Min退化成纯平均分。需求上限应该反映业务实际能消费的速率不是物理上限。解决思路是先无控运行一段时间统计每个队列在无拥塞时的平均吞吐量然后用这个统计值乘以一个系数比如1.2倍作为需求上限。这样既避免了浪费也给了业务一定的突发空间。6. 工程落地的注意事项与经验心得6.1 注意总吞吐量下降Max-Min天然不以总吞吐量最大化为目标。有些业务方看到总吞吐量掉了5%就急眼这时候你要提前打预防针Max-Min保的是最差用户的体验下限代价可能就是一点点总量损失。如果总量损失不能接受可以考虑把Max-Min和最大吞吐量做加权——比如用α比例公平Proportional Fairness折中或者设定保底池弹性池两段分配。6.2 注意最小保证额度的误用不要把Max-Min计算结果直接当服务等级协议来承诺。有一个案例我记得很清楚某团队把Max-Min算出的保障带宽写进了SLA结果有一路用户实际需求很小计算出来的保障额度也很小客户不干了认为我们歧视小用户。后来改成承诺相对优先级而不是绝对带宽并在合同中写明保障带宽随系统总负载动态调整矛盾迎刃而解。Max-Min结果是一个给定时间点、给定总容量下的优化解不是永久不变的固定指标。6.3 注意与拥塞控制的协同Max-Min是在资源够不够分这个问题上做决策但它解决不了发送端不配合的问题。如果你的网络是TCP/IP环境发送端的拥塞窗口控制会主动退避这会干扰你辛苦算出来的公平额度。比较优雅的做法是在网络边缘设备上做带宽整形用令牌桶限速让发送端的实际发送速率被卡在Max-Min额度附近而不是一厢情愿地只在接收端做统计。6.4 注意算法的收敛性与实时性当用户数非常多比如做基站调度时上千个用户/流每轮都做全量排序会带来较大开销。工程上常用两种手段使用堆结构维护最小需求缺口把找最小值从 O(n) 降到 O(log n)。不追求每次调度都精确收敛而是用增量更新的方式在上一轮结果的基础上做小步调整即滑动水填充牺牲少量精确性换取实时性。实测下来增量更新在稳定状态下能做到误差在2%以内对绝大多数业务完全够用。7. 常见问题速查现象可能原因解决方案所有用户拿到的都一样看不出差别需求上限设置过大未进入用户依次退出的过程根据业务实际无拥塞吞吐量重新设定需求上限某个用户长期被饿死分配不到资源该用户权重被其他策略压制或需求上限过低检查其他调度策略是否覆盖了Max-Min配额必要时把权重逻辑收敛总吞吐量严重下降业务方投诉把过多资源优先给了无关紧要的低需求用户引入比例公平或弹性池机制限制Max-Min承保的用户范围计算结果抖动剧烈额度频繁变化需求上限统计周期过短或用户集合频繁增删对需求上限做平滑滤波如EMA并设置用户活跃集合的冻结时间系统CPU占用高调度时延大全量重算频率过高算法复杂度未优化优化为优先队列实现评估增量更新方案8. 最后的实操提醒个人体会是Max-Min公平性最难得的不是算法本身而是敢把最差用户纳入业务KPI的设计勇气。很多系统嘴上说要公平实际KPI全是99分位时延平均吞吐量没人关心那个被边缘化的用户是否连续超时。Max-Min至少给了你一个数学上自洽、逻辑上站得住的框架去回应你有没有偏心这种灵魂拷问。另外一个实用技巧对Max-Min做单元测试时别光拿标准案例跑专门构造一些极端case——比如某个用户需求为0、某个用户需求大于总容量、所有用户需求之和小于总容量。这几个case能一次性暴露算法里除以零死循环提前退出三大bug。我第一版Python实现就是在需求为0的场景上翻车的现在这个版本已经加了对应的保护逻辑。如果你打算把Max-Min用在自己的系统里我的建议是别急着全量替换现有调度策略。先把它放进灰度环境对比替换前后最差用户的分位数指标观察一段时间后再决定是否扩大覆盖面。资源分配的优化是一个持续博弈的过程Max-Min只是给了你的博弈一个更聪明的初始牌面剩下的还是靠持续监控和调参。