PHPAPI分布式锁怎么实现

wen PHP项目 14

本文目录导读:

PHPAPI分布式锁怎么实现

  1. Redis分布式锁(推荐)
  2. 使用场景示例
  3. 其他方案对比
  4. 最佳实践建议
  5. 注意事项

在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();
}

注意事项

  1. 锁超时时间:根据业务逻辑执行时间合理设置,避免锁自动释放后其他进程获取到锁
  2. 唯一标识:使用UUID或uniqid作为锁值,防止误删其他进程的锁
  3. 原子操作:释放锁时必须使用Lua脚本确保原子性
  4. 重试机制:对于高竞争场景,实现带退避的重试策略
  5. 监控告警:记录锁获取失败次数,设置告警阈值

对于PHP API项目,建议首选Redis分布式锁,简单高效且能满足大多数场景需求,如果需要更高的可用性,可以考虑使用Redlock算法或引入ZooKeeper。

抱歉,评论功能暂时关闭!