PHP实现分布式锁方案

wen PHP项目 2

PHP实现分布式锁方案:从Redis到ETCD,架构师必读的7种实战策略

PHP实现分布式锁方案

目录导读

  1. 为什么PHP需要分布式锁?——并发场景的困境
  2. 分布式锁的三大核心原则(CP原则解读)
  3. 基于Redis SETNX的经典实现(附代码陷阱)
  4. Redlock算法——高可用与争议并存
  5. 基于数据库的乐观锁与悲观锁(适合中小项目)
  6. ZooKeeper/ETCD临时节点方案(强一致性首选)
  7. 锁的续期、防误删与可重入设计(实战高阶技巧)
  8. 高频问答:锁超时、主从切换、性能损耗如何解决?

为什么PHP需要分布式锁?

当多个PHP-FPM进程(或集群节点)同时操作同一资源(如秒杀库存、订单状态、缓存重建)时,本地flock() 已失效,分布式锁的本质是“跨进程互斥”,确保同一时刻只有一个执行者持有临界区权限。

分布式锁的三大核心原则

  • 互斥性:任何时刻只能有一个客户端持有锁。
  • 安全性:锁必须自动释放(过期时间),避免死锁。
  • 容错性:当持有锁的节点崩溃时,其他节点仍能获取锁。

方案一:基于Redis SETNX的经典实现

// 加锁(PHP + Redis 扩展)
$lockKey = 'lock:order:123';
$token = uniqid('', true); // 唯一令牌,防止误删
$result = $redis->set($lockKey, $token, ['NX', 'EX' => 10]);
if ($result) {
    // 执行业务代码
    try { 
        // 业务...
    } finally {
        // Lua脚本原子释放锁(校验令牌)
        $script = 'if redis.call("get",KEYS[1])==ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end';
        $redis->eval($script, [$lockKey, $token], 1);
    }
}

陷阱:必须用set原子命令(避免SETNX+EXPIRE两步);释放锁必须用Lua校验令牌,防止删除别人新获取的锁。

方案二:Redlock算法——高可用与争议

Redlock要求向5个独立Redis节点申请锁,超过半数成功即视为持有,PHP实现可用predisredlock-php客户端。 争议点:Martin Kleppmann指出,Redis主从切换时可能丢失锁,建议仅在非强一致场景使用(如缓存击穿保护),不适用于账户扣款。

方案三:数据库乐观锁与悲观锁(按需选择)

  • 悲观锁SELECT ... FOR UPDATE(需在事务中),适合低频操作。
  • 乐观锁UPDATE stock SET version = version + 1 WHERE id = ? AND version = ?,适合高并发状态机流转。 PHP实现注意:PDO连接必须长期保持,且要设置超时时间(innodb_lock_wait_timeout)。

方案四:ETCD/ZooKeeper强一致性锁

使用ETCD的Lease + Create API,创建带租约的临时节点,通过Campaign实现leader选举。

// 简化版(使用 etcd-php 客户端)
$grant = $client->grant(10); // 租约10秒
$client->put('/lock/order', 'client1', ['lease' => $grant->getID()]);
// 业务完成后 revoke 租约

优势:无主从切换问题,自动续期机制完善,适合分布式事务调度。

锁的续期、防误删与可重入设计

  • 续期:守护协程(Workerman/Swoole)每3秒检查剩余TTL,若小于5秒则EXPIRE延长。
  • 可重入:用hash记录持锁线程的计数或进程ID,同进程内可再次获取。
  • 防重放攻击:令牌必须包含随机数+业务标识,禁止使用固定值。

高频问答:锁超时、主从切换、性能损耗?

Q1:业务执行时间超过锁过期时间,导致锁提前被释放?
A:必须实现自动续期(如Redisson的watch dog),若业务与锁无关(如写入结果集),可设置足够大的过期时间,并在finally中释放。

Q2:Redis主从切换时锁丢失怎么办?
A:强烈要求高可靠性使用ETCD或ZooKeeper,若预算有限,可通过Redlock+双写机制缓解,但无法绝对避免。

Q3:高并发下锁性能损耗如何评估?
A:Redis锁的QPS可达百万级别,加锁逻辑性能损失<1ms;数据库锁通常耗时5-20ms(含网络+行锁),适合低频操作。

Q4:PHP-FPM多进程场景下要不要用锁?
A:单机多进程用flock+apc即可,仅跨节点部署时才需升级为Redis锁。

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