发布于2026-07-11 阅读(0)
扫一扫,手机访问
分布式限流这件事,技术方案看起来不少,但要真正实现“强一致性”,路其实挺窄的。Redis 配合 Lua 脚本是目前比较成熟的解法,但这里面有个关键前提——限流的全部判断逻辑,必须老老实实缩在 Lua 脚本里完成,Ja va 层只负责“安全调用”,不要掺和任何状态判断。
问题的核心就几句话:Redis + Lua 脚本本身具备原子性,能保证强一致;而 Ja va 嘛,离了脚本,任何“先查后判再写”的操作都会破坏原子性,漏放或误拒只是时间问题。
实践中经常能看到这样的场景:Ja va 先 stringRedisTemplate.opsForValue().get() 拿到当前计数,接着 if (count >= max) throw new RuntimeException() 做个判断,最后才 incr() 加一。这三步之间,竞态窗口敞开着,多个请求可能同时读到旧值、同时判定未超限、同时写入,结果就是实际 QPS 轻松翻倍。
差别在哪里?
redis.call() 天然是原子的,不会被打断。pipeline 或者事务(multi/exec),也只保证命令序列不被穿插,却无法保证“读-算-写”这个逻辑在并发下不被干扰。简单说,Lua 才是集装箱,Ja va 只该是运货的卡车司机,不用替集装箱验货。
EVAL 与 EVALSHA 的选择要点到了生产环境,关于脚本调用方式,有个很实际的建议:用 EVALSHA,别每次都传完整 Lua 长串。
EVAL 每次发送整段脚本,网络包体积不小,尤其脚本里塞了注释和空格的时候更加明显。EVALSHA 只传 40 个字符的哈希,带宽和解析开销都降下来了。SCRIPT LOAD 注册一下脚本,否则 EVALSHA 返回 NOSCRIPT 错误——这个错误容易被忽略,可一旦忽略,限流就形同虚设。RedisTemplate.execute(RedisScript, …) 默认走 EVALSHA,但它要求脚本对象已经预热,也就是要提前执行一次 loadScript()。总结下来就是:EVALSHA 省带宽、省解析,但预热这件事千万别省。
拿最常见的令牌桶限流来说,KEYS[1] 自然就是限流 key,比如 "rate:uid:123"。而 ARGV 通常按这样几个参数传进去:
ARGV[1]:当前毫秒时间戳,用 System.currentTimeMillis() 传进去,用来计算这段时间内生成了多少令牌。ARGV[2]:桶的容量 max_permits,比如 100。ARGV[3]:令牌生成速率 rate,单位是“个/秒”,比如 50 就表示每 20 毫秒补一个。ARGV[4]:本次申请的令牌数,通常为 1。虽然支持批量获取,但大多数场景固定传 1 就够。这里有个容易被忽略的细节:有人喜欢用 redis.call('TIME') 在 Redis 内部获取时间,但返回的是秒 + 微秒。且不说时区、精度的问题,它和业务系统的时钟很难对齐,时间漂移会直接干扰限流的准确度。所以更推荐的做法是,让客户端(Ja va 端)把时间戳作为参数传进去。
脚本写对了,这还只是第一步。Ja va 层配置上稍有不慎,强一致性照样会崩溃。
StringRedisTemplate 的序列化器必须明确设为 StringRedisTemplate.setDefaultSerializer(new StringRedisSerializer()),不然传进去的 KEYS 和 ARGV 可能变成乱码,Lua 里 tonumber(ARGV[1]) 返回 nil,限流直接就垮了。Long 类型,比如 1 表示通过,0 表示拒绝。Ja va 接收时泛型必须用 Long.class,要是图省事用了 Integer.class,ClassCastException 就会找上门。max-active 设得太小,会导致 EVAL 请求排队,表面上看起来限流在生效,其实是连接池成了新瓶颈。timeout 设得太短,正常脚本执行到一半就被中断,直接抛 RedisCommandTimeoutException。redis.log()。虽然它不阻塞,但高频打日志会严重拖慢 Redis 主线程,而且日志内容也传不回 Ja va,调试时反而帮倒忙。说来也怪,写出正确的 redis.call('zadd', ...) 并不难,真正的难点在于让整个链路——从 Ja va 时钟、序列化、连接池,到 Redis 的 script cache 和 clock drift——全都对齐在同一个语义下。稍微偏差一点,“强一致”就真的只剩一个名字了。
上一篇:怎么解释 volatile 只能保证单次读写的原子性,而无法保证 i++ 这种复合操作的原子性?
下一篇:如何通过 ReentrantReadWriteLock 的锁升降级语义分析其在处理“读多写少且需强一致性”场景下的优势
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8