Redis集群构建令牌桶限流器的一致性问题咨询
结论
这个问题确实存在,但不是Redis Cluster集群模式本身的固有缺陷,绝大多数计数错误都是实现方式不当、集群特性适配不到位导致的。单节点部署时所有限流逻辑跑在单实例上,Lua脚本的原子执行能力可以保证令牌生成、扣减的计算完全准确;集群模式下只要适配好分片规则、规避一致性风险点,限流器完全可以做到误差在业务可接受范围内。
集群模式下令牌计数错误的核心诱因
- 跨槽操作破坏原子性
不少开发者实现令牌桶时,会把「当前剩余令牌数」「上次令牌补充时间戳」拆成两个独立的字符串key存储,没有用Hash Tag做同槽绑定。Redis Cluster是按哈希槽分片存数据的,这两个独立key大概率会落在不同的主节点上,原本需要原子执行的令牌计算Lua脚本要么直接报跨槽错误,要么被客户端拆成多次请求转发到不同节点执行,逻辑被其他请求打断,自然会出现计数偏差。 - 读写分离引入的主从延迟误差
部分场景为了降低主节点读压力,会给Redis客户端配置读请求路由到从节点的策略。但Redis Cluster的主从复制是异步的,正常情况下延迟在毫秒级,流量高、节点负载重时延迟会飙升到数百毫秒甚至秒级。请求如果从从节点读到旧的令牌计数、旧的时间戳,计算出的可用令牌数会比实际值高,直接导致超放行。 - 故障转移阶段的数据丢失误差
当主节点突发宕机触发自动故障转移时,主节点上还没同步给从节点的最新令牌扣减记录会直接丢失,新选举出的主节点会用旧的计数数据处理请求,短时间内会出现令牌超发的问题。
对应修复方案
- 最低改造成本方案:强制同槽绑定+主节点读写
改造现有限流器的key规则:同一个限流维度的所有相关数据,全部存在带统一Hash Tag的key下,比如接口限流的key统一写为ratelimit:{api_pay}:v1,把剩余令牌数、上次更新时间、桶容量、生成速率都存在这个key对应的Hash结构里,保证所有相关数据落在同一个哈希槽对应的主节点上。同时客户端关闭限流场景的从节点读配置,所有读写请求全部路由到key所在的主节点,单节点上执行的Lua脚本依然可以保证原子性,正常运行状态下不会出现计算错误。
注意:这个方案无法规避故障转移时的少量数据丢失问题,适合允许短时间、小范围流量超发的非核心业务场景,改造成本几乎可以忽略。 - 精度与性能平衡方案:Redis批量申请+本地预扣减
把令牌桶的逻辑拆成两层:Redis侧只存桶的全局元数据(桶容量、令牌生成速率、上次全局更新时间),用单key的Lua脚本实现原子的批量令牌申请逻辑——服务实例本地令牌用完时,一次性向Redis申请一批令牌(比如单次申请100个),申请成功后把令牌存在服务本地的原子变量里,后续请求直接在本地内存做令牌扣减,本地令牌耗尽后再发起下一次Redis申请。
这种模式下对Redis的访问量会降低两个数量级,所有Redis操作都是单key单主节点操作,完全适配集群分片规则;哪怕出现主从延迟、故障转移,最多只会导致单批次申请的令牌出现超发,误差范围完全可控,性能也比全量请求查Redis的模式高很多,是绝大多数生产环境的首选方案。 - 零误差方案:强一致存储承载核心计数
如果业务对限流精度要求极高(比如合规要求、支付场景的强限流,完全不允许超阈值放行),就不要依赖Redis的最终一致性模型,可以把限流核心计数迁移到基于Raft等共识算法的强一致KV存储上,保证每次计数写入都经过多数节点确认后再返回,从底层规避一致性带来的计数误差。
内容的提问来源于stack exchange,提问作者Ethan
相关产品推荐
相关产品推荐

