rratelimiter必须调用trysetrate初始化才生效,仅getratelimiter返回空配置实例,tryacquire恒返false;需在@postconstruct中幂等调用,配合overall/per_client合理选型并手动构造业务key。

RRateLimiter 初始化必须调用 trySetRate 才生效
直接 new 或 getRateLimiter 得到的实例是空配置状态,tryAcquire 会始终返回 false,且不报错。必须显式调用 trySetRate 设置速率规则,Redis 中才会写入 rate、interval、type 三个字段。
常见错误现象:本地调试时一切正常,上生产后所有请求都被限流 —— 很可能是因为配置未成功写入 Redis(如网络不通、key 被误删、或 trySetRate 被放在了非启动路径里)。
-
trySetRate是幂等操作,重复调用不会覆盖已有配置(底层用hsetnx),适合在应用启动时统一初始化 - 若需动态调整速率,应改用
setRate(非原子,有竞态风险,慎用) - 推荐在
@PostConstruct或 Spring@Bean初始化方法中执行,确保单例限流器只设一次
RateType.OVERALL 和 RateType.PER_CLIENT 的行为差异
RateType.OVERALL 表示全局限流:所有服务实例共享同一套令牌桶,适用于“整个接口每秒最多 N 次”这类强一致性要求;RateType.PER_CLIENT 则按客户端维度隔离(实际是按 Redisson 客户端 ID 区分),适合“每个 IP / 每个用户每秒最多 N 次”,但注意它不自动识别 HTTP 请求来源,需手动拼 key。
容易踩的坑:误以为 PER_CLIENT 能自动绑定 IP 或 userId —— 它只认当前 RedissonClient 实例的唯一标识(如 redisson-12345),和业务无关。真要按 IP 限流,key 应该是 "rate:ip:" + request.getRemoteAddr(),类型仍用 OVERALL。
- 全局限流 key 命名建议带业务前缀,如
"api:reset_password:limiter",避免不同接口共用一个 key 导致互相干扰 - 如果部署了多个 RedissonClient(比如用了不同 Config),
PER_CLIENT会为每个 client 创建独立桶,无法达到预期限流效果
tryAcquire 的超时与阻塞行为要主动控制
tryAcquire(1) 默认不等待,获取不到令牌立即返回 false;而 acquire(1) 会无限期阻塞直到拿到令牌 —— 这在 Web 接口里极易引发线程池耗尽。生产环境几乎从不用 acquire。
更安全的做法是使用带超时的重载:tryAcquire(1, 100, TimeUnit.MILLISECONDS),即最多等 100ms。超过则视为限流,快速失败。
- 不要依赖默认超时(底层是 0ms),否则
tryAcquire等价于纯判断,无法应对突发流量下的“稍等一下就可通过”的场景 - 超时时间不宜设太长(如 > 500ms),否则用户感知卡顿,且增加线程堆积风险
- 若需支持“排队等待”,应结合业务降级策略(如返回排队中 + WebSocket 推送),而非靠
acquire阻塞线程
令牌桶刷新依赖 Redis 时间,跨时区或时钟漂移会导致异常
RRateLimiter 内部通过 Lua 脚本读取 Redis 服务端时间(redis.call('time'))计算令牌余量。如果 Redis 服务器时间比应用服务器快/慢较多(> 1s),会出现“明明没用完却限流”或“已超限仍放行”现象。
典型表现:定时任务触发限流突然变严、凌晨部署后限流失效、多机房部署时部分节点限流不准。
- 务必确保 Redis 服务端开启 NTP 同步,禁止手动修改系统时间
- 避免将 Redis 部署在虚拟机或容器中且未配置时钟同步(如 Docker 默认不继承宿主机时间)
- 测试阶段可用
redis-cli time和date +%s%N对比两端毫秒级时间差











