php实现redis锁
PHP 实现 Redis 锁的方法
使用 SETNX 命令实现基本锁
在 PHP 中可以通过 Redis 的 SETNX 命令实现简单的分布式锁。SETNX 是 "SET if Not eXists" 的缩写,只有当键不存在时才会设置值。
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$lockKey = 'resource_lock';
$lockExpire = 10; // 锁过期时间(秒)
// 尝试获取锁
$isLocked = $redis->setnx($lockKey, time() + $lockExpire);
if (!$isLocked) {
// 检查锁是否过期
$lockTime = $redis->get($lockKey);
if (time() > $lockTime) {
// 锁已过期,删除并重新获取
$redis->del($lockKey);
$isLocked = $redis->setnx($lockKey, time() + $lockExpire);
}
}
if ($isLocked) {
try {
// 执行业务代码
} finally {
// 释放锁
$redis->del($lockKey);
}
} else {
// 获取锁失败
}
使用 SET 命令带选项实现更安全的锁
Redis 2.6.12 之后,SET 命令支持更多选项,可以实现更安全的锁机制:

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$lockKey = 'resource_lock';
$lockExpire = 10; // 锁过期时间(秒)
$lockToken = uniqid(); // 唯一标识符
// 尝试获取锁
$isLocked = $redis->set(
$lockKey,
$lockToken,
['nx', 'ex' => $lockExpire]
);
if ($isLocked) {
try {
// 执行业务代码
} finally {
// 确保只释放自己的锁
if ($redis->get($lockKey) === $lockToken) {
$redis->del($lockKey);
}
}
} else {
// 获取锁失败
}
使用 RedLock 算法实现分布式锁
对于更复杂的分布式环境,可以使用 RedLock 算法,它需要在多个独立的 Redis 实例上获取锁:

function acquireLock($redisInstances, $resource, $ttl, $retryCount = 3, $retryDelay = 200) {
$token = uniqid();
$lockSuccessCount = 0;
for ($i = 0; $i < $retryCount; $i++) {
$lockSuccessCount = 0;
foreach ($redisInstances as $redis) {
if ($redis->set($resource, $token, ['nx', 'px' => $ttl])) {
$lockSuccessCount++;
}
}
// 检查是否在大多数实例上获取了锁
if ($lockSuccessCount > (count($redisInstances) / 2)) {
return $token;
}
// 释放已经获取的锁
foreach ($redisInstances as $redis) {
$redis->del($resource);
}
usleep($retryDelay * 1000);
}
return false;
}
function releaseLock($redisInstances, $resource, $token) {
foreach ($redisInstances as $redis) {
$script = '
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
';
$redis->eval($script, [$resource, $token], 1);
}
}
使用 Redis 扩展的锁功能
某些 PHP Redis 扩展(如 phpredis)提供了内置的锁方法:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
if ($redis->rawCommand('LOCK', 'my_lock', 'NX', 'EX', 10)) {
try {
// 执行业务代码
} finally {
$redis->rawCommand('UNLOCK', 'my_lock');
}
}
使用 Predis 库实现 Redis 锁
如果使用 Predis 客户端库,可以这样实现:
require 'vendor/autoload.php';
$client = new Predis\Client();
$lockKey = 'resource_lock';
$lockExpire = 10;
$lockToken = uniqid();
$lockAcquired = $client->set($lockKey, $lockToken, 'EX', $lockExpire, 'NX');
if ($lockAcquired) {
try {
// 执行业务代码
} finally {
$script = '
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
';
$client->eval($script, 1, $lockKey, $lockToken);
}
}
注意事项
- 确保为锁设置合理的过期时间,避免死锁
- 使用唯一标识符(token)来确保只释放自己的锁
- 考虑锁续期机制,如果业务执行时间可能超过锁的过期时间
- 在高并发环境下,重试机制和退避策略很重要
- 分布式环境下,时钟同步问题需要考虑
- 评估业务对锁的需求强度,有些场景可能不需要强一致性锁






