必须用Redis原子命令SET key value NX EX seconds加锁,值用uniqid保证唯一,解锁用Lua脚本校验value后删除,超时任务需续期并校验,锁失败应合理降级或重试。

ThinkPHP 5.1 在分布式环境下防止定时任务重复执行或并发写库,必须用真正可靠的 Redis 分布式锁——不能只靠 setNx(),也不能手动拆成两步(先 setNx 再 expire),否则极易死锁或误删锁。
加锁必须原子:一步完成设置 + 过期 + 唯一标识
TP5.1 的 Redis::set() 支持数组参数,可直接调用 Redis 原子命令 SET key value NX EX seconds:
- 键名要业务语义清晰,比如
lock:order_sync、task:clean_expired_logs - 值必须是每个请求唯一的字符串,推荐用
uniqid('', true),避免多进程同一毫秒生成相同 ID - 必须指定过期时间(如
['nx', 'ex' => 60]),防止进程崩溃后锁永远不释放 - 不要用
$redis->setNx($key, 1); $redis->expire($key, 60);—— 中间崩溃就卡死
解锁必须校验:Lua 脚本保障原子删除
直接 $redis->delete($key) 是高危操作,可能删掉别人刚续上的锁:
- 解锁前必须确认当前锁的 value 和自己加锁时设的一致
- 必须用 Lua 脚本执行“判断 + 删除”,TP5.1 的
eval()可直接调用 - 脚本示例:
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" - 调用时传入
[$lockKey], [$lockValue],确保只删自己设的锁
业务耗时超时?得加自动续期机制
如果数据库慢查询、远程调用等导致执行时间超过锁过期时间,锁会被其他进程抢走,造成并发执行:
立即学习“PHP免费学习笔记(深入)”;
- 可在加锁后启动一个独立协程或后台任务,定期用相同 value 给锁续期(
$redis->expire($key, 60)) - 续期前仍需用 Lua 校验 value 是否匹配,避免续错别人的锁
- 更稳妥的做法是预估最大执行时间,把锁过期设为该值的 2–3 倍,并配合超时中断逻辑
别忽略锁失败后的处理逻辑
抢锁失败不是异常,而是正常并发场景:
- 返回 false 后不要直接报错或跳过,应明确提示“任务已被其他节点执行”或记录日志
- 对非强实时任务(如日志清理),可直接 return;对关键任务(如支付回调),可延迟重试(如 sleep(1) 后再 tryLock)
- 避免无限制循环重试,建议加最大尝试次数或退避策略(如指数增长等待)



















