用SET而非INCR实现验证码限频,因INCR+EXPIRE存在竞态导致key永不过期或窗口超限;应结合SET的EX/NX原子设值与独立计数key,并确保其同步过期。

为什么用 SET 而不是 INCR 实现验证码限频
直接用 INCR + EXPIRE 容易出现竞态:INCR 成功但 EXPIRE 失败,导致 key 永不过期;或者并发请求下多次 INCR 后才设过期,实际窗口超出限制。更稳妥的做法是用 SET 的 EX 和 NX 参数原子地写入并设过期,再配合单独的计数 key(如 verify:limit:138****1234)做递增——但注意,这个计数 key 也必须同步设过期,否则残留。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 对手机号生成唯一限频 key,例如
verify:limit:+86138****1234 - 首次请求用
SET verify:limit:+86138****1234 "1" EX 60 NX,成功则允许发码;失败说明已存在,再用INCR+EXPIRE verify:limit:+86138****1234 60补过期时间(EXPIRE返回 0 表示 key 不存在,需跳过) - 校验前先查
GET verify:limit:+86138****1234,若值 ≥5,直接拒绝,不查验证码本身
验证码存储该用 SET 还是 SETEX?
SETEX 看似简洁,但它不支持条件写入(比如“仅当不存在时才设值”),而验证码必须防重放:同一手机号短时间内重复请求,不应覆盖旧码,否则旧验证码失效,用户体验断层。正确做法是用 SET 的 XX(仅更新已有 key)或 NX(仅新建)控制行为。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 发码时用
SET verify:code:+86138****1234 "8742" EX 300 NX—— 只有没码时才存,保证旧码在有效期内仍可用 - 若业务允许“刷新验证码”,则改用
SETEX verify:code:+86138****1234 300 "1953",但要同步重置限频计数(DEL verify:limit:+86138****1234) - 避免用
SET不带参数裸写,否则 key 永不过期,Redis 内存泄漏
Golang 中用 redis.UniversalClient 还是 redis.Client?
除非你明确需要 Redis Cluster 或哨兵自动发现,否则别用 redis.UniversalClient。它抽象层厚、错误堆栈模糊、连接池配置不直观,出问题时连是连不上集群还是单节点超时都难判断。验证码这种强实时、低延迟场景,直连单节点 Redis 更可控。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
redis.NewClient初始化,显式传&redis.Options{Addr: "localhost:6379", Password: "", DB: 0} - 设置
SetReadTimeout和SetWriteTimeout均为 500ms,防止网络抖动拖慢整个 HTTP 请求 - 务必调用
client.Ping(ctx).Err()做启动时健康检查,别等发码时才发现 Redis 不通
校验时为什么必须用 GETDEL 而不是 GET + DEL
并发请求下,两个 goroutine 同时 GET 到相同验证码,再各自 DEL,结果只有一个能真正删除,另一个会误判“验证码正确但已被使用”。GETDEL 是原子操作:取值同时删 key,天然解决这个问题。Go redis 客户端从 v9.0 开始支持 GetDel 方法,低于此版本需用 Eval 手写 Lua 脚本模拟。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 优先升级到
github.com/redis/go-redis/v9,直接调client.GetDel(ctx, "verify:code:+86138****1234").Val() - 如果还在用 v8,写 Lua:
redis.Eval(ctx, "local v = redis.call('GET', KEYS[1]); redis.call('DEL', KEYS[1]); return v", []string{"verify:code:+86138****1234"}) - 注意
GETDEL返回空字符串表示 key 不存在——这可能是用户输错、超时、或已被其他请求消耗,统一按“验证码错误”处理即可
真正麻烦的不是命令怎么写,而是限频窗口和验证码有效期的交叠逻辑:比如用户 2:58 发请求,3:00 过期,但 2:59 又点一次,此时限频 key 还在,验证码却已刷新——得靠 NX 和 XX 的组合策略来兜住,而不是靠时间硬对齐。


















