把Token桶和漏桶放在一起比参数调优,本质上是在比两套完全不同的数学逻辑。Token桶的调优核心是处理突发,漏桶的调优核心是压制突发。很多人在CC防护里把这两者混用,参数照搬,结果要么误杀正常请求,要么被峰值流量打穿。问题不出在算法本身,而出在对参数背后控制逻辑的理解偏差。

Token桶算法的三个核心参数与调优逻辑

Token桶算法只有三个关键参数:令牌生成速率(rate)、桶容量(burst)、以及时间窗口粒度。rate决定了系统在稳态下允许的请求通过速率,burst决定了系统能容忍的最大瞬时突发量。很多人只调rate,忽略burst,这是CC防护里最常见的错误。

rate的设置不能直接等同于业务承载上限。假设后端真实QPS上限是1000,rate设成1000就是找死。因为Token桶允许短时间消耗完burst里囤积的所有令牌,瞬时通过量会远超1000。正确的做法是rate设成后端真实上限的70%到80%,留出缓冲区给突发消耗。burst的设置则要看业务特征。API类业务,burst设成rate的1.5到2倍足够;电商秒杀类业务,burst可能需要设到rate的5到10倍,但前提是后端能扛住这波瞬时冲击。

时间窗口粒度这个参数经常被忽略。Token桶的令牌生成是离散的还是连续的,取决于实现。如果令牌每秒生成一次,那在这一秒内的前999毫秒可能都没有令牌可用,导致请求被误拒。所以生产环境必须使用亚毫秒级的令牌生成粒度,或者直接采用无锁的令牌生成算法,让令牌补充变成连续行为。否则你的CC防护策略在微观时间尺度上会出现周期性误杀,这种问题用压测很难复现,但线上用户会感受到间歇性的访问失败。

漏桶算法的参数陷阱与正确调优方式

漏桶算法同样三个核心参数:漏出速率(leak rate)、桶容量(capacity)、以及请求处理时延上限。漏桶和Token桶最大的区别在于,漏桶强制平滑输出,无论进来的流量多猛,出去的流量永远是恒定速率。这个特性在CC防护里是把双刃剑。

leak rate的设置逻辑和Token桶的rate完全不同。Token桶的rate是“允许通过的上限”,漏桶的leak rate是“实际处理的下限”。如果leak rate设得比后端处理能力低,那桶里永远在积水,最终必然溢出丢请求。正确的做法是leak rate等于后端实测的稳定处理能力,注意是稳定处理能力,不是峰值能力。桶容量capacity则决定了系统能承受的突发排队长度。capacity除以leak rate,就是用户在最坏情况下需要等待的最长时间。

这里有个巨大的坑。很多人把漏桶的capacity设得很大,以为这样能“兜住”更多请求。但实际上capacity越大,用户在桶满之前的排队时延就越长。CC攻击里有一种慢速攻击,就是故意把桶占满但不超时,让正常请求排队排到超时。所以capacity的设置必须和业务可接受的最大响应时延强绑定。公式很简单:capacity = leak rate × 最大可接受排队时延。如果业务要求P99响应时间不超过200ms,leak rate是1000 QPS,那capacity最多只能设200。超过这个值,你的漏桶不是在防护CC,而是在帮攻击者放大延迟伤害。

两种算法在CC攻击场景下的参数动态调整策略

CC攻击的流量特征不是固定的。慢速CC、快速CC、混合CC,对参数的要求完全不同。静态参数配置在复杂攻击面前基本等于裸奔。Token桶应对慢速CC有天然优势,因为慢速CC的请求速率往往低于rate,但通过长连接占用资源。这时候需要调的不是rate,而是和连接时长相关的辅助参数,比如单个令牌的消耗可以按连接时长加权。如果一个连接持续超过正常业务时长,即使请求频率低,也应该消耗更多令牌。

漏桶面对快速CC时反而更稳,因为不管攻击流量多大,输出速率被物理限制住了。但问题在于,如果leak rate设得太保守,正常流量会和攻击流量一起排队,造成大范围误伤。这时候需要引入优先级队列,在漏桶入口做请求分类。已认证用户、正常行为特征的请求走优先队列,疑似攻击流量走低优队列。两个队列共享同一个漏桶容量,但优先队列可以抢占低优队列的排队位置。这个设计让漏桶从“无差别限流”升级成“有差别防护”,参数调优的重心从单一的leak rate变成了队列权重分配。

动态调整方面,两种算法都需要和实时监控联动。Token桶的rate和burst应该根据后端服务的实时负载做浮动。比如CPU使用率超过70%时,rate自动下调20%;连接数超过阈值时,burst收紧50%。漏桶的leak rate同样需要动态化,但调整幅度要更保守,因为漏桶的速率变化会直接影响所有在排队的请求的预期等待时间。频繁调整leak rate会让用户体验变得不可预测。

参数调优的实战对比与组合使用方案

单独使用Token桶或漏桶,在复杂CC攻击面前都会有盲区。Token桶管不住持续的大流量慢速攻击,漏桶扛不住短时脉冲式的暴力CC。所以生产环境的最佳实践是两级串联:入口用Token桶做突发控制,后端用漏桶做平滑处理。这种组合下,参数调优的逻辑彻底变了。

入口Token桶的rate可以设得相对激进,比如后端承载上限的90%,burst设成rate的3到5倍。它的职责不是精确限流,而是削掉最尖锐的流量毛刺。那些通过Token桶的请求,进入漏桶后会被进一步整形。漏桶的leak rate设成后端稳定处理能力的85%到90%,capacity根据业务延迟容忍度严格控制。两级之间的缓冲队列长度也需要调优,太短则漏桶饥饿,太长则整体延迟增加。

具体参数调优可以用以下逻辑做基准线测试。先用阶梯加压的方式测出后端服务的真实QPS拐点,记这个值为Q。Token桶rate初始值设为0.9Q,burst设为2Q。漏桶leak rate设为0.85Q,capacity设为leak rate乘以0.1秒。然后用混合流量模型压测,包含正常业务流量、脉冲式攻击流量、慢速CC流量三种成分。观察两个指标:正常请求的P99延迟和攻击请求的拦截率。如果P99延迟超标,优先减小漏桶capacity或降低Token桶burst。如果拦截率偏低,优先降低Token桶rate,或者在漏桶入口增加行为检测的权重系数。

还有一个容易被忽视的细节是令牌消耗和漏桶排队的计量单位。大部分实现以请求次数为单位,但CC攻击里,一个请求可能携带大量Payload,消耗的后端资源远大于普通请求。所以参数调优时应该考虑把计量单位从“请求数”改成“资源消耗估算值”。比如按请求的Body大小、处理耗时、数据库查询次数等维度加权计算出一个消耗分,Token桶按消耗分扣除令牌,漏桶按消耗分计算排队长度。这样参数设置就不再是拍脑袋的QPS数字,而是和后端资源真实消耗挂钩的物理量。

监控指标与参数验证的闭环

参数调优不是一次性工作,必须有监控闭环。Token桶需要监控的核心指标是令牌消耗速率、突发消耗频率、以及因令牌不足被拒绝的请求比例。如果突发消耗频率很高,说明burst设小了或者业务本身波动大,需要调整。如果拒绝比例突然飙升但后端负载正常,说明rate设得太保守或者攻击流量在增大。漏桶需要监控的核心指标是桶内平均排队长度、排队请求的P99等待时间、以及溢出的请求比例。排队长度持续接近capacity,说明leak rate跟不上实际需求。溢出比例间歇性出现,可能是GC停顿或者后端服务抖动导致的处理能力瞬时下降。

验证参数是否合理的一个实用方法是混沌工程。在生产环境低峰期,故意注入模拟CC流量,观察参数组合的实际表现。Token桶的burst是否能在不打死后端的前提下吸收掉模拟攻击的脉冲,漏桶的排队时延是否在业务可接受范围内,这些都需要在真实环境下验证。压测环境测出来的数据和生产环境差一个数量级是常事,因为生产环境的网络栈、连接数、数据库状态都和压测环境完全不同。

最后说一个反直觉的结论。在很多场景下,参数调优的瓶颈不在算法本身,而在配置的灵活性。如果Token桶和漏桶的参数只能通过配置文件静态设置,不支持运行时动态调整,那再好的调优策略也落不了地。CC攻击的流量特征变化速度是秒级的,参数调整必须能跟上这个节奏。所以选择限流组件时,是否支持热更新参数、是否暴露Metrics接口、是否能和现有监控系统联动,这些工程化能力比算法本身的理论优势重要得多。