go中用incr生成自增id的核心陷阱是键生命周期失控:redis重启导致id重置、未设过期致内存泄漏、多服务共用key引发冲突;必须用lua脚本原子封装incr+expireat,并通过业务维度命名key、本地atomic兜底及强监控保障高可用。

Go 中用 INCR 生成自增 ID 的核心陷阱
直接调用 INCR 看似简单,但生产环境里最容易出问题的是键重置和并发竞争——不是命令不原子,而是你没管好键的生命周期。
-
INCR确实原子,但 Redis 重启后键消失,ID 从 0 开始,历史 ID 就可能重复 - 没设置过期时间,长期累积大量无用 key(比如按天分片的
icr:order:2026:07:14),内存持续上涨 - 多个服务共用同一个 key(如都用
global:id:order),业务隔离失效,互相干扰
如何用 LUA 脚本保证「读+写+过期」三步原子性
单纯靠 INCR + EXPIRE 两步走,在并发下存在窗口期:A 执行了 INCR,还没来得及 EXPIRE,B 又读到了旧值。必须用 LUA 把逻辑锁死。
- 脚本里先
INCR,再EXPIREAT(用绝对时间戳,避免时钟漂移影响) - key 命名要带业务维度和租户标识,例如
id:order:tenant_abc,不能裸用id:order - 注意 Lua 中
redis.call("INCR", KEYS[1])返回的是字符串,需显式转数字再拼接
local current = redis.call("INCR", KEYS[1])
redis.call("EXPIREAT", KEYS[1], ARGV[1])
return tonumber(current)
go-redis v8 的 Eval 调用细节
v8 版本取消了 Send 和 Do 的低级接口,Eval 是唯一推荐方式,但参数传法容易错。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
ctx必须带超时,否则 Redis 卡住会拖垮整个服务;建议设为500 * time.Millisecond -
keys参数必须是[]string,不能是单个 string;哪怕只用一个 key,也要写成[]string{"id:order:abc"} -
argv是可变参数,过期时间必须用time.Now().Add(24*time.Hour).Unix()计算后传 int64,不能传 time.Time
为什么不能只依赖 Redis 自增?关键兜底点在哪
Redis 是缓存,不是数据库。当它不可用时,ID 生成不能直接失败——业务写不进去了。
- 本地 fallback:用
sync/atomic维护一个 per-process 的递增 counter,仅限短时降级(比如 Redis 故障 5 分钟内) - 必须记录日志:每次 fallback 触发都要打 warning 日志,并告警;长期 fallback 说明 Redis 集群已不可靠
- 绝不能 fallback 到 UUID 或随机数——破坏了“趋势递增”这个核心诉求,MySQL B+ 树索引性能会掉一截
真正难的不是写对一行 INCR,而是想清楚:当 Redis 挂了、网络分区了、主从延迟了,你的 ID 还能不能保序、不重复、不中断。这些边界情况,往往在压测和线上故障时才暴露。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










