连载中 6/20

分布式限流:Redis 加 Lua 的原子计数

2026-07-08 · 2190 阅读 · 0 评论 · 0 赞

竞态跟着计数器一起搬家

上一篇的结论:要精确控制总量与每人配额,计数器必须集中到 Redis。但「读计数、判断、写计数」三个动作一旦分开执行,竞态就来了——十个实例同时读到 199,判断都通过,都放行,限额 200 放进 210。这套剧情读者在分布式锁系列第 2 篇已经见过(校验和删除分两步的坑),解法也一样:把判断与计数打包成 Lua 脚本,在 Redis 单线程里原子执行

三个算法的 Redis 版

固定窗口版第 2 篇已给(INCR 加 PEXPIRE 打包),不再重复。滑动窗口版第 3 篇也给了(ZSET 加 ZREMRANGEBYSCORE),适合低频高价值接口。这里重点补令牌桶的分布式版——工程里最常用又最讲究的一个:

-- KEYS[1]=桶key  ARGV[1]=速率  ARGV[2]=桶容量  ARGV[3]=本次申请数
local time = redis.call("TIME")                 -- 用 Redis 的时钟!
local now = time[1] * 1000 + math.floor(time[2] / 1000)
local bucket = redis.call("HMGET", KEYS[1], "tokens", "ts")
local tokens = tonumber(bucket[1]) or ARGV[2]   -- 首次:满桶
local ts = tonumber(bucket[2]) or now
local delta = math.max(0, now - ts)             -- 距上次的时间差
tokens = math.min(tonumber(ARGV[2]), tokens + delta / 1000 * tonumber(ARGV[1]))
if tokens >= tonumber(ARGV[3]) then
    tokens = tokens - tonumber(ARGV[3])
    redis.call("HMSET", KEYS[1], "tokens", tokens, "ts", now)
    redis.call("PEXPIRE", KEYS[1], 60000)       -- 兜底过期
    return 1
end
redis.call("HMSET", KEYS[1], "tokens", tokens, "ts", now)
return 0

细节一:时钟必须统一

脚本第一行就是本篇最重要的细节:令牌补充按时间差计算,时间差的两端必须来自同一个时钟。如果应用传自己的 now,实例 A 的时钟快 2 秒、实例 B 的慢 1 秒,同一个桶的时间差算出来忽大忽小,令牌凭空多发或少发。用 Redis 的 TIME 命令取时间(Redis 3.2+ 允许脚本里调用),所有实例对齐到 Redis 的时钟——分布式系统里「逻辑时钟对齐」永远优于「信任各节点物理时钟」,RedLock 论战(分布式锁系列第 5 篇)讲的时钟漂移问题,在这里提前绕开了。

细节二:Redis 挂了,放行还是拒绝

限流依赖 Redis,Redis 不可用时怎么办?两个方向都有道理,按业务性质选:fail-open(放行)——限流器失效宁可超卖,也不能让全站跟着限流器一起瘫,适合保护自家资源的场景:超卖一点总好过全站不可用;fail-close(拒绝)——限不住就坚决不放,适合花钱的出口:短信、支付调第三方,刷穿就是真金白银。折中方案是本地兜底限流:Redis 挂了自动降级到单机 RateLimiter(阈值按总量除以实例数),可用性由单机限流接力——粗糙但保命。

细节三:高并发下的 RT 优化

每次请求一次 Redis 往返(约 1 毫秒),万级 QPS 下 Redis 网络成为新瓶颈,两个经典优化:批量取牌——一次 Lua 取 10 个令牌回本地,本地逐个消费,RT 降到十分之一,代价是突发粒度变粗(本实例可能囤牌);两层限流——本地先用宽松阈值(总量/实例数再加余量)拦截明显超标的,通过的才去 Redis 精确校验,九成的拒绝在本机完成,Redis 只受理边缘流量

方案精度Redis 压力适用
固定窗口边界突刺低(两个命令)粗粒度总量控制
令牌桶速率精确,允许突发中(读写桶状态)通用默认,接口级限流
ZSET 滑动窗口零误差高(与请求量成正比)低频高价值接口

分布式限流的内核到此完整:原子脚本、统一时钟、故障策略、性能分层。但还有一个位置没安放——这些闸门该装在系统的哪一层?Nginx 前置在流量入口,网关聚合一揽子规则,应用内精细到方法——下一篇从最外层的网关讲起。

503

10 年全栈工程师 · 503咖啡馆主理人

#分布式限流#Redis#Lua#令牌桶#时钟统一#fail-open

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 4 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞