PHP项目PHP如何对接分布式锁组件

wen PHP项目 31

本文目录导读:

PHP项目PHP如何对接分布式锁组件

  1. 基于Redis的分布式锁
  2. 基于ZooKeeper的分布式锁
  3. 使用etcd分布式锁
  4. 使用成熟的分布式锁组件
  5. 最佳实践建议
  6. 注意事项:

在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

注意事项:

  1. 锁超时:必须设置合理的TTL,防止死锁
  2. 锁释放:确保在finally中释放锁,使用try-finally模式
  3. 可重入性:如果需要可重入锁,需要额外处理
  4. 性能考虑:Redis性能最优,ZooKeeper可靠性最高
  5. 时钟同步:使用Redlock算法时要注意时钟漂移问题
  6. 监控告警:对锁获取失败等情况进行监控

选择哪种方案取决于你的具体需求:高并发场景推荐Redis,需要强一致性推荐ZooKeeper或etcd,分布式环境复杂推荐使用成熟的第三方组件。

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