本文目录导读:

在PHP项目中对接分布式锁组件,通常有以下几种主流方案,我会逐一说明实现方式和适用场景:
基于Redis的分布式锁
使用Predis/PhpRedis + Redlock算法
<?php
use Predis\Client;
class RedisDistributedLock
{
private $redis;
private $lockKey;
private $lockValue;
private $ttl;
public function __construct($redisConfig)
{
$this->redis = new Client($redisConfig);
}
/**
* 获取锁
*/
public function acquire($key, $ttl = 30)
{
$this->lockKey = "lock:{$key}";
$this->lockValue = uniqid('', true);
$this->ttl = $ttl;
// 使用SET NX EX 原子操作
$result = $this->redis->set(
$this->lockKey,
$this->lockValue,
'NX',
'EX',
$ttl
);
return $result !== null;
}
/**
* 释放锁(使用Lua脚本保证原子性)
*/
public function release()
{
$script = '
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
';
return $this->redis->eval(
$script,
1,
$this->lockKey,
$this->lockValue
);
}
/**
* 带重试的锁获取
*/
public function acquireWithRetry($key, $ttl = 30, $retryDelay = 200, $maxRetries = 3)
{
$attempts = 0;
while ($attempts < $maxRetries) {
if ($this->acquire($key, $ttl)) {
return true;
}
$attempts++;
usleep($retryDelay * 1000);
}
return false;
}
}
// 使用示例
$lock = new RedisDistributedLock([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
if ($lock->acquire('order:123')) {
try {
// 执行临界区代码
echo "处理订单123";
} finally {
$lock->release();
}
}
使用Laravel框架的Redis锁
<?php
use Illuminate\Support\Facades\Redis;
use Illuminate\Cache\RedisLock;
// Laravel 内置锁
$lock = Redis::lock('order:123', 30);
if ($lock->get()) {
try {
// 执行业务逻辑
ProcessOrder::dispatch();
} finally {
$lock->release();
}
}
// 或者简写方式
Redis::lock('order:123', 30)->block(5, function () {
// 自动释放锁
ProcessOrder::dispatch();
});
基于ZooKeeper的分布式锁
<?php
// 需要安装 php-zookeeper 扩展
class ZookeeperDistributedLock
{
private $zookeeper;
private $lockPath;
private $nodePath;
public function __construct($hosts)
{
$this->zookeeper = new Zookeeper($hosts);
}
/**
* 创建锁
*/
public function createLock($lockName)
{
$this->lockPath = "/locks/{$lockName}";
// 创建持久节点作为锁根
if (!$this->zookeeper->exists($this->lockPath)) {
$this->zookeeper->create(
$this->lockPath,
'',
[
[
'perms' => Zookeeper::PERM_ALL,
'scheme' => 'world',
'id' => 'anyone'
]
]
);
}
}
/**
* 获取锁
*/
public function acquire($lockName)
{
$this->createLock($lockName);
// 创建临时顺序节点
$this->nodePath = $this->zookeeper->create(
$this->lockPath . '/lock-',
session_id(),
[
[
'perms' => Zookeeper::PERM_ALL,
'scheme' => 'world',
'id' => 'anyone'
]
],
Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE
);
// 获取所有子节点
$children = $this->zookeeper->getChildren($this->lockPath);
sort($children);
// 判断是否为最小节点
$currentNode = basename($this->nodePath);
return $children[0] === $currentNode;
}
/**
* 释放锁
*/
public function release()
{
if ($this->nodePath && $this->zookeeper->exists($this->nodePath)) {
$this->zookeeper->delete($this->nodePath);
$this->nodePath = null;
}
}
public function __destruct()
{
$this->release();
$this->zookeeper->close();
}
}
使用etcd分布式锁
<?php
// 需要安装 etcd-php 库
use Etcd\Client;
class EtcdDistributedLock
{
private $client;
private $lockKey;
private $leaseId;
public function __construct($endpoints)
{
$this->client = new Client($endpoints);
}
/**
* 获取锁
*/
public function acquire($key, $ttl = 30)
{
$this->lockKey = "locks/{$key}";
// 创建租约
$response = $this->client->leaseGrant(['TTL' => $ttl]);
$this->leaseId = $response['ID'];
// 尝试获取锁(原子操作)
$response = $this->client->txn([
'compare' => [
[
'target' => 'CREATE',
'key' => $this->lockKey,
'result' => 'EQUAL',
'value' => 0
]
],
'success' => [
[
'requestPut' => [
'key' => $this->lockKey,
'value' => uniqid(),
'lease' => $this->leaseId
]
]
],
'failure' => []
]);
if ($response['succeeded']) {
return true;
}
$this->leaseRevoke();
return false;
}
/**
* 释放锁
*/
public function release()
{
if ($this->lockKey) {
$this->client->delete($this->lockKey);
}
$this->leaseRevoke();
}
private function leaseRevoke()
{
if ($this->leaseId) {
$this->client->leaseRevoke(['ID' => $this->leaseId]);
$this->leaseId = null;
}
}
}
使用成熟的分布式锁组件
安装组件(以Laravel为例)
composer require graze/dog-statsd # 或者 composer require php-etl/redis-lock
使用通用分布式锁库
<?php
// 使用 symfony/lock 组件
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\RedisStore;
use Predis\Client;
class DistributedLockService
{
private $factory;
public function __construct($redisConfig)
{
$redis = new Client($redisConfig);
$store = new RedisStore($redis);
$this->factory = new LockFactory($store);
}
/**
* 执行带锁的任务
*/
public function executeWithLock($lockName, callable $callback, $ttl = 30)
{
$lock = $this->factory->createLock($lockName, $ttl);
if (!$lock->acquire()) {
throw new \RuntimeException("无法获取锁: {$lockName}");
}
try {
return $callback();
} finally {
$lock->release();
}
}
/**
* 等待获取锁
*/
public function executeWithWait($lockName, callable $callback, $ttl = 30, $waitSeconds = 5)
{
$lock = $this->factory->createLock($lockName, $ttl);
$lock->acquire(true); // true表示阻塞等待
try {
return $callback();
} finally {
$lock->release();
}
}
}
最佳实践建议
封装统一的锁服务
<?php
interface DistributedLockInterface
{
public function acquire($key, $ttl = 30);
public function release();
public function acquireWithBlock($key, $ttl = 30, $waitSeconds = 5);
}
class DistributedLockFacade
{
private static $instance;
private $implementations = [];
public static function getInstance($type = 'redis')
{
if (!isset(self::$instance)) {
self::$instance = new self();
}
return self::$instance->getImplementation($type);
}
public function getImplementation($type)
{
if (!isset($this->implementations[$type])) {
$this->implementations[$type] = $this->createImplementation($type);
}
return $this->implementations[$type];
}
private function createImplementation($type)
{
switch ($type) {
case 'redis':
return new RedisDistributedLock(config('database.redis.default'));
case 'zookeeper':
return new ZookeeperDistributedLock(config('zookeeper.hosts'));
case 'etcd':
return new EtcdDistributedLock(config('etcd.endpoints'));
default:
throw new \InvalidArgumentException("不支持的锁类型: {$type}");
}
}
}
// 使用示例
$lock = DistributedLockFacade::getInstance('redis');
if ($lock->acquire('payment:order:123', 30)) {
try {
// 支付处理
$paymentService->process();
} finally {
$lock->release();
}
}
配置示例(.env文件)
LOCK_DRIVER=redis # Redis配置 REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_PASSWORD= REDIS_DATABASE=0 # ZooKeeper配置 ZOOKEEPER_HOSTS=127.0.0.1:2181,127.0.0.1:2182 # etcd配置 ETCD_ENDPOINTS=http://127.0.0.1:2379,http://127.0.0.1:2380
注意事项:
- 锁超时:必须设置合理的TTL,防止死锁
- 锁释放:确保在finally中释放锁,使用try-finally模式
- 可重入性:如果需要可重入锁,需要额外处理
- 性能考虑:Redis性能最优,ZooKeeper可靠性最高
- 时钟同步:使用Redlock算法时要注意时钟漂移问题
- 监控告警:对锁获取失败等情况进行监控
选择哪种方案取决于你的具体需求:高并发场景推荐Redis,需要强一致性推荐ZooKeeper或etcd,分布式环境复杂推荐使用成熟的第三方组件。