本文目录导读:

在PHP项目中实现分布式锁,主要有以下几种常用方法,我会从简单到复杂依次介绍:
基于Redis的分布式锁(最常用)
简单实现
class RedisDistributedLock {
private $redis;
private $lockKey;
private $lockValue;
private $ttl;
public function __construct($redis, $lockKey, $ttl = 10) {
$this->redis = $redis;
$this->lockKey = $lockKey;
$this->lockValue = uniqid('', true); // 唯一标识
$this->ttl = $ttl;
}
// 获取锁
public function acquire() {
// SET NX EX 原子操作
$result = $this->redis->set(
$this->lockKey,
$this->lockValue,
['NX', 'EX' => $this->ttl]
);
return $result === true || $result === 'OK';
}
// 释放锁(使用Lua脚本保证原子性)
public function release() {
$script = <<<LUA
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
LUA;
$result = $this->redis->eval($script, [$this->lockKey, $this->lockValue], 1);
return $result === 1;
}
}
// 使用示例
$lock = new RedisDistributedLock($redis, 'order_lock:123', 30);
if ($lock->acquire()) {
try {
// 执行临界区代码
processOrder();
} finally {
$lock->release();
}
}
RedLock算法(高可用实现)
class RedLock {
private $redisInstances = [];
private $quorum;
private $retryDelay = 200; // 毫秒
private $retryCount = 3;
public function __construct(array $redisInstances) {
$this->redisInstances = $redisInstances;
$this->quorum = count($redisInstances) / 2 + 1;
}
public function lock($resource, $ttl = 1000) {
$token = uniqid('', true);
$startTime = microtime(true) * 1000;
for ($i = 0; $i < $this->retryCount; $i++) {
$successCount = 0;
$elapsedTime = microtime(true) * 1000 - $startTime;
foreach ($this->redisInstances as $instance) {
if ($elapsedTime >= $ttl) break;
$result = $instance->set(
$resource,
$token,
['NX', 'PX' => $ttl]
);
if ($result) {
$successCount++;
}
}
$elapsedTime = microtime(true) * 1000 - $startTime;
if ($successCount >= $this->quorum && $elapsedTime < $ttl) {
return ['token' => $token, 'resource' => $resource];
}
// 释放已获取的锁
$this->unlockInternal($resource, $token);
// 等待重试
usleep($this->retryDelay * 1000);
}
return false;
}
public function unlock($lockData) {
$this->unlockInternal($lockData['resource'], $lockData['token']);
}
private function unlockInternal($resource, $token) {
$script = <<<LUA
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
LUA;
foreach ($this->redisInstances as $instance) {
$instance->eval($script, [$resource, $token], 1);
}
}
}
基于数据库的分布式锁
MySQL行级锁实现
class MysqlDistributedLock {
private $pdo;
private $lockTable = 'distributed_locks';
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function acquire($lockName, $timeout = 10) {
try {
// 使用GET_LOCK函数(MySQL 5.7+)
$stmt = $this->pdo->prepare("SELECT GET_LOCK(:lockName, :timeout)");
$stmt->execute([
':lockName' => $lockName,
':timeout' => $timeout
]);
return $stmt->fetchColumn() === 1;
} catch (PDOException $e) {
return false;
}
}
public function release($lockName) {
$stmt = $this->pdo->prepare("SELECT RELEASE_LOCK(:lockName)");
$stmt->execute([':lockName' => $lockName]);
return $stmt->fetchColumn() === 1;
}
}
基于数据库行锁的实现
class DatabaseRowLock {
private $pdo;
private $lockTable = 'locks';
public function acquire($lockName, $ttl = 30) {
$token = uniqid('', true);
try {
// 使用悲观锁
$this->pdo->beginTransaction();
// 查询并锁定行
$stmt = $this->pdo->prepare(
"SELECT * FROM {$this->lockTable}
WHERE lock_name = :lockName
AND (expires_at IS NULL OR expires_at < NOW())
FOR UPDATE"
);
$stmt->execute([':lockName' => $lockName]);
if ($stmt->rowCount() > 0) {
// 更新锁信息
$updateStmt = $this->pdo->prepare(
"UPDATE {$this->lockTable}
SET token = :token,
expires_at = DATE_ADD(NOW(), INTERVAL :ttl SECOND),
locked_at = NOW()
WHERE lock_name = :lockName"
);
$updateStmt->execute([
':token' => $token,
':ttl' => $ttl,
':lockName' => $lockName
]);
$this->pdo->commit();
return $token;
}
// 插入新锁
$insertStmt = $this->pdo->prepare(
"INSERT INTO {$this->lockTable} (lock_name, token, locked_at, expires_at)
VALUES (:lockName, :token, NOW(), DATE_ADD(NOW(), INTERVAL :ttl SECOND))"
);
$insertStmt->execute([
':lockName' => $lockName,
':token' => $token,
':ttl' => $ttl
]);
$this->pdo->commit();
return $token;
} catch (Exception $e) {
$this->pdo->rollBack();
return false;
}
}
public function release($lockName, $token) {
$stmt = $this->pdo->prepare(
"DELETE FROM {$this->lockTable}
WHERE lock_name = :lockName AND token = :token"
);
$stmt->execute([
':lockName' => $lockName,
':token' => $token
]);
return $stmt->rowCount() > 0;
}
}
基于ZooKeeper的分布式锁
class ZooKeeperLock {
private $zk;
private $root = '/locks';
public function __construct($hosts) {
$this->zk = new ZooKeeper($hosts);
// 确保根节点存在
if (!$this->zk->exists($this->root)) {
$this->zk->create($this->root, '', [
'flags' => ZooKeeper::EPHEMERAL
]);
}
}
public function acquire($lockName, $timeout = 10) {
$lockPath = $this->root . '/' . $lockName;
// 创建临时顺序节点
$createdPath = $this->zk->create($lockPath . '/lock-', '', [
'flags' => ZooKeeper::EPHEMERAL | ZooKeeper::SEQUENCE
]);
$startTime = time();
while (true) {
// 获取所有子节点
$children = $this->zk->getChildren($lockPath);
sort($children);
// 检查是否是第一个
$nodeName = basename($createdPath);
if ($nodeName === $children[0]) {
return $createdPath; // 获取锁成功
}
// 监听前一个节点
$prevNode = $children[array_search($nodeName, $children) - 1];
// 检查超时
if (time() - $startTime > $timeout) {
// 删除创建的节点
$this->zk->delete($createdPath);
return false; // 获取锁超时
}
// 等待直到前一个节点被删除
$this->zk->get($lockPath . '/' . $prevNode, function() {
// 前一个节点被删除,重新检查
});
}
}
public function release($lockPath) {
return $this->zk->delete($lockPath);
}
}
基于etcd的分布式锁
use Etcd\Client;
class EtcdLock {
private $client;
private $ttl = 10;
public function __construct($endpoints) {
$this->client = new Client($endpoints);
}
public function acquire($lockName) {
$key = '/locks/' . $lockName;
$value = uniqid('', true);
// 使用租约(lease)
$lease = $this->client->grant($this->ttl);
try {
// 尝试创建锁
$this->client->put($key, $value, [
'lease' => $lease->ID,
'prevKV' => true
]);
return $value; // 获取锁成功
} catch (\Exception $e) {
// 锁已被占用
$lease->revoke();
return false;
}
}
public function release($lockName, $value) {
$key = '/locks/' . $lockName;
// 只有持有者才能释放
$response = $this->client->get($key);
if ($response && $response->kvs[0]->value === $value) {
$this->client->delete($key);
return true;
}
return false;
}
public function refresh($lockName, $value) {
$key = '/locks/' . $lockName;
$response = $this->client->get($key);
if ($response && $response->kvs[0]->value === $value) {
$this->client->put($key, $value, [
'lease' => $response->kvs[0]->lease
]);
return true;
}
return false;
}
}
推荐实践建议
选择建议
class LockFactory {
public static function create($type, $config) {
switch ($type) {
case 'redis':
return new RedisDistributedLock(
$config['redis'],
$config['lockKey'],
$config['ttl'] ?? 30
);
case 'mysql':
return new MysqlDistributedLock($config['pdo']);
case 'zookeeper':
return new ZooKeeperLock($config['hosts']);
case 'etcd':
return new EtcdLock($config['endpoints']);
default:
throw new InvalidArgumentException("Unsupported lock type");
}
}
}
使用模版方法模式
abstract class AbstractDistributedLock {
abstract public function acquire();
abstract public function release();
public function execute(callable $callback) {
if ($this->acquire()) {
try {
return $callback();
} finally {
$this->release();
}
}
throw new LockFailedException('Failed to acquire lock');
}
}
// 使用示例
$lock = new RedisDistributedLock($redis, 'process_lock:123', 30);
$result = $lock->execute(function() {
// 业务逻辑
return processImportantTask();
});
注意事项
- 锁超时处理:设置合理的TTL,避免死锁
- 锁粒度:尽量精细,只锁定必要资源
- 重试机制:获取锁失败时的重试策略
- 监控告警:监控锁的获取和释放情况
- 降级策略:缓存降级或服务降级
对于大多数PHP项目,推荐使用Redis实现分布式锁,既简单又高效,如果需要更高的可靠性,可以考虑RedLock算法或ZooKeeper方案。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播