仅在工作时间限流可通过 golang.org/x/time/rate 结合时间判断实现:先用 time.Now().In(loc).Hour() 和 Weekday() 判断是否处于周一至周五 9:00–18:00(左闭右开),再决定是否调用 r.Allow()。

直接用 golang.org/x/time/rate + 时间判断就能实现「仅在特定时间段内限流」,不需要改写整个限流逻辑,也不依赖 Redis 或外部存储。
怎么让限流只在工作时间生效
核心是把限流开关和当前时间耦合起来:不是所有请求都走限流器,而是先判断是否处于限流时段,再决定是否调用 r.Allow()。
- 用
time.Now().Hour()和time.Now().Weekday()判断当前是否落在目标窗口(比如周一至周五 9:00–18:00) - 时段判断逻辑必须放在中间件最开头,避免无谓调用
r.Allow()或 Redis 查询 - 注意时区问题 ——
time.Now()默认用本地时区,生产环境应统一用time.UTC或显式加载时区:loc, _ := time.LoadLocation("Asia/Shanghai") - 时段边界要明确:9:00 开始限流,还是 9:00:01?建议用左闭右开区间,例如
hour >= 9 && hour
按路径+时段组合限流的常见错误
很多人想对 /api/report 只在每天 2:00–5:00 限流,结果发现限流没生效或误杀其他接口。根本原因是 key 设计或执行顺序不对。
- 别把时段逻辑硬编码进
sync.Map的 key 里(比如"report_2-5"),时段是动态的,key 应该固定,控制逻辑放代码里 - 如果用了 per-IP 限流,确保时段判断在取
c.ClientIP()之后,但不要在判断前就初始化 limiter 实例 —— 否则空跑一次初始化 - 别在限流中间件里调用
c.Next()两次:一次在时段外放行,一次在时段内放行+限流,容易导致 handler 执行两遍 - 时段外的请求,直接
c.Next()即可;时段内的请求,才走r.Allow()分支
rate.Limiter 实例要不要按天重建
不用。一个 *rate.Limiter 实例本身不保存时间语义,它只管令牌生成节奏。只要你的时段判断逻辑正确,复用同一个 limiter 完全安全。
立即学习“go语言免费学习笔记(深入)”;
-
rate.NewLimiter(rate.Every(1*time.Second), 5)表示“每秒最多 5 次”,这个速率恒定,和当前几点无关 - 真正变化的是“是否启用它”,而不是“重置它” —— 所以没必要每天 new 一个新实例,更不要用定时器去 replace map 里的值
- 唯一需要清理的,是用
sync.Map缓存了 per-IP 或 per-path limiter 的场景:那些长时间没访问的 key 可以定期清理,但和时段无关
测试时段限流时最容易漏掉的点
本地开发时用 time.Now() 测试,上线后发现限流没触发,大概率是服务器时间和你预期的时区不一致。
- 打印日志时别只打
time.Now().String(),加一句time.Now().Location().String()确认时区 - 单元测试里别依赖真实时间,用
func() time.Time注入时间获取函数,方便 mock - 如果用 Docker 部署,检查容器内
/etc/localtime是否挂载正确,或者启动时加-e TZ=Asia/Shanghai - 时段逻辑上线前,用 curl 手动模拟非工作时间请求,确认返回码是 200 而不是 429
时段限流真正的复杂点不在限流算法本身,而在时间判定与业务路由的耦合粒度 —— 是全局开关?某个 group?还是单个 handler?这个决策比怎么写 Lua 脚本影响更大。


















