不能直接用incr生成全局唯一id,因其在redis cluster中仅作用于单个slot,跨slot无法保证原子递增;必须通过hash tag(如{idgen})强制所有key落同一slot,否则lua脚本会因crossslot错误被拒绝。

Redis INCR 命令是核心,但直接用 INCR 有单点风险
ThinkPHP 本身不内置分布式 ID 生成器,得靠 Redis 的原子性操作来构建。最直接的想法是用 INCR 对一个 key 自增,比如 INCR global_id_seq。这在单 Redis 实例下可行,但一旦用了 Redis Cluster,INCR 就会报错 —— 因为 Cluster 要求 key 必须落在同一 slot,而 INCR 是跨节点不安全的命令。所以不能无脑写 $redis->incr('id'),得先确认部署模式。
实操建议:
- 用
phpinfo()或Redis::getHost()查当前 Redis 连接是否指向单机或哨兵(Sentinel),而非 Cluster; - 若已上 Cluster,必须改用
EVAL+ Lua 脚本,把 key hash tag 固定到一个 slot,例如用{id}作为 key:INCR {id}_seq; - ThinkPHP 的
think\cache\driver\Redis默认不支持EVAL,需通过$this->redis->handler()->eval(...)获取原生 Redis 实例再调用。
用 Lua 脚本保证原子性,避免并发重复 ID
单纯 GET + INCR 两步走,在高并发下会丢 ID 或重复。必须把“读旧值、判断是否超限、自增、返回”打包成 Lua 脚本执行。ThinkPHP 中调用方式如下:
$lua = "local current = redis.call('INCR', KEYS[1]) if current > tonumber(ARGV[1]) then redis.call('SET', KEYS[1], ARGV[2]) return ARGV[2] else return current end";
$result = $this->redis->handler()->eval($lua, ['{id}_seq'], 1, 999999999, 1);
说明:
-
KEYS[1]是带 hash tag 的 key,确保路由一致; -
ARGV[1]是最大值(防溢出),ARGV[2]是重置值(如归零后从 1 开始); - 脚本里用
redis.call()而非redis.pcall(),因逻辑简单且需明确失败; - 注意 ThinkPHP 缓存驱动的
handler()返回的是Predis\Client或Redis原生对象,行为略有差异,Predis 需用evalSha配合scriptLoad才高效,但首次可直用eval。
ID 格式要带时间戳和机器标识,别只依赖纯自增
纯 INCR ID 没有时间信息,无法排序,也缺乏业务区分能力。推荐组合方案:时间戳(秒级)+ 机器 ID(如配置的 server_id)+ 序列号(Redis 自增)。例如生成 1717023456_2_12345。
实操要点:
- 序列号部分仍走 Redis
INCR,但 key 按小时或天分片,如{id}_20240530,减轻单 key 热点; - 机器 ID 不要用 IP(可能变),建议在
config/redis.php里配'worker_id' => 2; - 避免用
microtime(true)拼毫秒级时间戳——Redis 自增本身不保序,毫秒内多请求仍可能乱序;秒级足够,再靠序列号补足; - 如果需要 Snowflake 类似结构(41bit 时间 + 10bit 机器 + 12bit 序列),得自己位运算,但 PHP 的
int在 32 位系统上撑不住 63bit,务必用gmp或字符串拼接,别硬转int。
缓存失效与重置逻辑必须显式处理
Redis key 永不过期看似省事,但线上可能被手动清空、主从切换丢失、或运维误操作。若没兜底,ID 会突然从 1 开始,导致主键冲突。
应对方式:
- 每次生成前先
EXISTS {id}_seq,不存在则SET {id}_seq 0再INCR; - 不要依赖
SETNX初始化 —— 它不阻塞,多个并发都可能进初始化分支; - 更稳妥的是在应用启动时(如命令行
php think id:init)预设初始值,并记录到数据库配置表中,后续以 DB 为准做校验; - 日志里务必打上生成 ID 的时间、key、返回值,一旦发现 ID 跳变或归零,能快速定位是 Redis 异常还是脚本逻辑错。
真正麻烦的不是写几行 INCR,而是当 Redis 节点闪断、Cluster 槽迁移、或者运维半夜 flushdb 之后,你的 ID 是否还能连续、唯一、可追溯。这些边界比语法重要得多。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











