rate.Limiter无法实现日粒度限流,因其基于滑动时间窗口且无日期概念;自然日限流需用Redis按日期维度建key、Lua原子执行INCR+EXPIRE,并注意时区与中间件顺序。

为什么不能只用 rate.Limiter 做日粒度限流
rate.Limiter 是按时间窗口滑动的(比如每秒/每分钟),它内部维护的是「最近 N 秒内允许多少次」,不记录历史累计值。你无法靠它回答「这个 IP 今天总共调用了多少次」——它连「今天」这个概念都没有。
常见错误是把 rate.Every(24*time.Hour) 当成「每天一次」,实际效果是「任意连续 24 小时内最多一次」,窗口会滚动,根本不是自然日。
真正要统计「2026-08-19 00:00:00 到 23:59:59 的总次数」,必须:
- 有带日期维度的存储(如 Redis key 含
20260819) - 每次请求都做原子自增 + 过期设置(
INCR+EXPIRE配合) - 在增长前检查是否已达阈值,而不是事后判断
用 Redis 实现自然日计数的关键写法
核心是让每个 key 对应一个自然日 + 维度组合,例如:api:daily:ip:192.168.1.100:20260819 或 api:daily:uid:abc123:20260819。Redis 的 INCR 和 EXPIRE 必须在一个 Lua 脚本里原子执行,否则可能超限。
示例 Lua 脚本(传入 key 和 limit):
local current = redis.call('INCR', KEYS[1])
if current == 1 then
redis.call('EXPIRE', KEYS[1], 86400)
end
if current > tonumber(ARGV[1]) then
return 0
end
return 1
Go 中调用(使用 redis.Eval):
在中间件里生成当天日期字符串:dateStr := time.Now().Format("20060102"),拼出 key,再执行脚本。返回 0 就直接 c.AbortWithStatusJSON(http.StatusTooManyRequests, ...)。
中间件注册顺序决定能否拿到用户标识
如果你要按登录用户(而非 IP)限日频次,c.GetString("user_id") 必须已存在。这意味着:
- 认证中间件(如 JWT 解析)必须在限流中间件之前注册
- 若认证失败或未登录,降级策略要明确:是按 IP 限?还是直接拒绝?不能留空字符串当 key,否则所有未登录用户共用一个桶
-
c.ClientIP()可能被伪造,务必提前配置r.SetTrustedProxies([]string{"10.0.0.0/8"})并启用X-Forwarded-For解析
别忽略过期和清理逻辑
Redis key 设置了 EXPIRE 86400 是为了自动清理,但要注意:
- 如果某天没人调用某个 IP 的接口,对应 key 不会产生,无需清理
- 但如果业务要求「保留最近 7 天数据用于审计」,就不能只靠 TTL,得额外用 Sorted Set 或独立 key 记录每日值
- 避免用
SCAN定期清理——高并发下会阻塞 Redis;自然过期是最轻量、最可靠的方式
真正容易被忽略的是:自然日切换时区问题。如果你的服务部署在 UTC,但业务要求按北京时间(UTC+8)算「一天」,就得用 time.Now().In(loc).Format("20060102"),其中 loc 是 time.LoadLocation("Asia/Shanghai")。否则凌晨 0 点到 8 点之间的请求会被记到错误的日期里。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











