php用redis实现分布式锁的核心是set nx ex原子加锁、lua脚本安全解锁、唯一value校验及合理续期,防止误删锁和死锁。

PHP 使用 Redis 实现分布式锁,核心是借助 Redis 的原子操作(如 SET 命令的 NX 和 EX 选项)来保证“加锁”操作的互斥性与自动过期,避免死锁。关键不在写多复杂,而在正确处理加锁、续期、解锁三个环节,尤其要防止误删他人锁。
用 SET NX EX 原子加锁
Redis 2.6.12+ 支持 SET key value [NX] [EX seconds],其中 NX 表示仅当 key 不存在时才设置,EX 指定过期时间(秒),二者组合可一步完成“判断+写入+过期”,天然具备原子性。
- 锁的 value 必须唯一(推荐用客户端 ID + 时间戳或 UUID),用于后续校验是否自己加的锁
- 过期时间要合理:太短易误释放;太长故障恢复慢。一般设为业务执行时间的 2–3 倍
- 不要用
SETNX + EXPIRE两步操作——中间可能崩溃导致 key 无过期,引发死锁
安全解锁:先校验再删除
解锁不能直接 DEL,否则可能删掉别人刚续上的锁。必须用 Lua 脚本保证“读 value → 判断是否匹配 → 删除”三步原子执行:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
- PHP 中通过
$redis->eval($script, [$key], [$value])调用该脚本 - 若返回 1 表示解锁成功;0 表示锁已失效或不属于当前客户端
- 绝不使用
GET + DEL两步手动判断——竞态条件下不可靠
应对长时间任务:支持自动续期(可选)
如果业务执行可能超锁过期时间(比如上传大文件、复杂计算),需在持有锁期间定期续期(renew),但要注意:
- 续期只能由加锁者执行,同样需校验 value 匹配
- 推荐用独立守护线程或定时器,每 1/3 过期时间检查并延长 TTL(如原设 30s,则每 10s 续一次)
- 续期命令:
GETSET key new_value不合适;应改用PEXPIREAT key (current_ms + ttl_ms)并再次校验 value - 更稳妥做法是把长任务拆成多个短任务,或改用 Redlock 等更重方案(适用于多 Redis 实例场景)
实际 PHP 示例(精简版)
以下基于 phpredis 扩展,不含异常重试和日志,聚焦逻辑主干:
// 加锁
$lockKey = "order:pay:" . $orderId;
$lockValue = uniqid() . ':' . time();
$ttl = 30; // 秒
if ($redis->set($lockKey, $lockValue, ['nx', 'ex' => $ttl])) {
try {
// 执行业务逻辑(如扣库存、生成订单)
processOrder($orderId);
} finally {
// 安全解锁
$script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
$redis->eval($script, [$lockKey], [$lockValue]);
}
} else {
throw new Exception("Lock failed, resource busy");
}
- 务必放在
try...finally中,确保无论成功失败都尝试解锁 - 生产环境建议封装成类,加入重试机制(如等待后重试)、超时控制、监控埋点
- 单机 Redis 场景下此方案足够;跨机房或高可用要求高时,考虑 Redlock 或 etcd
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











