本文目录导读:

在PHP中实现API分布式锁,主要有以下几种常见方案,这里以最推荐的Redis方案为例。
Redis分布式锁(推荐)
基础实现(使用SET NX EX)
class RedisDistributedLock {
private $redis;
private $lockKey;
private $lockValue;
private $ttl;
public function __construct($redis, $key, $ttl = 10) {
$this->redis = $redis;
$this->lockKey = "lock:{$key}";
$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 = '
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
';
return $this->redis->eval($script, [$this->lockKey, $this->lockValue], 1);
}
/**
* 尝试获取锁,带重试机制
*/
public function acquireWithRetry($maxRetries = 3, $retryDelay = 200000) {
$attempts = 0;
while ($attempts < $maxRetries) {
if ($this->acquire()) {
return true;
}
$attempts++;
usleep($retryDelay); // 微秒
}
return false;
}
}
Redlock算法实现(高可用)
class RedLock {
private $redisInstances = [];
public function __construct($redisConfigs) {
foreach ($redisConfigs as $config) {
$redis = new Redis();
$redis->connect($config['host'], $config['port']);
if (isset($config['password'])) {
$redis->auth($config['password']);
}
$this->redisInstances[] = $redis;
}
}
/**
* 获取锁(Redlock算法)
*/
public function lock($resource, $ttl = 10000) {
$startTime = microtime(true) * 1000;
$lockValue = uniqid('', true);
$quorum = floor(count($this->redisInstances) / 2) + 1;
$successCount = 0;
foreach ($this->redisInstances as $redis) {
try {
$result = $redis->set(
"lock:{$resource}",
$lockValue,
['NX', 'PX' => $ttl]
);
if ($result === true || $result === 'OK') {
$successCount++;
}
} catch (Exception $e) {
// 忽略连接失败
}
}
$elapsedTime = (microtime(true) * 1000) - $startTime;
// 检查是否获取到多数节点的锁,且耗时没有超过TTL
if ($successCount >= $quorum && $elapsedTime < $ttl) {
return $lockValue;
}
// 获取失败,释放所有锁
$this->unlock($resource, $lockValue);
return false;
}
/**
* 释放锁
*/
public function unlock($resource, $lockValue) {
$script = '
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
';
foreach ($this->redisInstances as $redis) {
try {
$redis->eval($script, ["lock:{$resource}", $lockValue], 1);
} catch (Exception $e) {
// 忽略错误
}
}
}
}
使用场景示例
API接口限流
class OrderAPI {
private $lock;
public function createOrder($userId, $productId) {
// 防止重复提交
$lockKey = "order:{$userId}:{$productId}";
$this->lock = new RedisDistributedLock($redis, $lockKey, 30);
try {
if (!$this->lock->acquire()) {
throw new Exception('请勿重复提交订单');
}
// 处理订单逻辑
$this->processOrder($userId, $productId);
} finally {
$this->lock->release();
}
}
}
秒杀场景
class SeckillService {
private $lock;
public function seckill($productId, $userId) {
// 分布式锁防止超卖
$lockKey = "seckill:{$productId}";
$this->lock = new RedisDistributedLock($redis, $lockKey, 5);
try {
if (!$this->lock->acquire()) {
// 返回友好提示
return ['code' => -1, 'msg' => '系统繁忙,请重试'];
}
// 检查库存
$stock = $redis->get("stock:{$productId}");
if ($stock <= 0) {
return ['code' => -1, 'msg' => '库存不足'];
}
// 扣减库存
$newStock = $redis->decr("stock:{$productId}");
if ($newStock < 0) {
$redis->incr("stock:{$productId}");
return ['code' => -1, 'msg' => '库存不足'];
}
// 创建订单
$this->createOrder($productId, $userId);
return ['code' => 0, 'msg' => '抢购成功'];
} finally {
$this->lock->release();
}
}
}
其他方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Redis | 高性能、原子操作支持好 | 需要Redis服务 | 大多数场景 |
| ZooKeeper | 强一致性、自动释放锁 | 性能不如Redis | 对一致性要求极高 |
| MySQL | 实现简单、无需额外服务 | 性能瓶颈、死锁风险 | 低并发场景 |
| Memcached | 高性能 | 不支持原子操作(需配合其他机制) | 简单锁需求 |
最佳实践建议
锁的粒度
// 细粒度锁(推荐)
$lockKey = "user:{$userId}:order";
// 粗粒度锁(不推荐)
$lockKey = "order:system";
锁超时处理
// 设置合理的超时时间
$lock = new RedisDistributedLock($redis, $key, 10); // 10秒超时
// 使用看门狗机制自动续期
class LockWithWatchDog {
private $lock;
private $watchDog;
public function __construct($redis, $key, $ttl = 10) {
$this->lock = new RedisDistributedLock($redis, $key, $ttl);
$this->startWatchDog();
}
private function startWatchDog() {
$this->watchDog = function() use ($key) {
while ($this->lock->isHeld()) {
$this->lock->expire($key, $ttl);
sleep($ttl / 3);
}
};
}
}
错误处理
try {
if (!$lock->acquire()) {
// 记录日志
Logger::warning("Failed to acquire lock: {$key}");
return response()->json(['error' => '系统繁忙'], 429);
}
// 业务逻辑
} catch (Exception $e) {
Logger::error("Lock operation failed: " . $e->getMessage());
throw $e;
} finally {
$lock->release();
}
注意事项
- 锁超时时间:根据业务逻辑执行时间合理设置,避免锁自动释放后其他进程获取到锁
- 唯一标识:使用UUID或uniqid作为锁值,防止误删其他进程的锁
- 原子操作:释放锁时必须使用Lua脚本确保原子性
- 重试机制:对于高竞争场景,实现带退避的重试策略
- 监控告警:记录锁获取失败次数,设置告警阈值
对于PHP API项目,建议首选Redis分布式锁,简单高效且能满足大多数场景需求,如果需要更高的可用性,可以考虑使用Redlock算法或引入ZooKeeper。