redis分布式锁(基于PHP实现)

具体实现代码

/**
 * 要求
 * 安全性(互斥性):在任意时刻,当且仅当只有一个客户端能持有锁
 *活性 A(无死锁):即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁
 *同一性:加锁和解锁必须保证为同一个客户端
 *活性 B (容错性):只要大部分的 Redis 节点正常运行,客户端就可以加锁和解锁
 */
class DistributedLock
{
    /**
     * @param $lockKey  key
     * @param $requestId value
     * @param $expireTime 过期时间
     * @return bool
     */
    //错误加锁示例1
    public function lock($lockKey, $requestId, $expireTime)
    {
        $setNx = \Redis::setnx($lockKey, $requestId);
        if ($setNx) {
            //此处乍一看这种方式并没有什么问题,但是由于是两条 redis 命令,So 不具有原子性,如果程序在执行完第一句 setnx 命令之后突然挂掉,那么会发生死锁,和设计原则相违背。因此不是最优解。
            //若在这里程序突然崩溃,则无法设置过期时间,将发生死锁
            $this->redis->expire($lockKey, $expireTime);
            return true;
        }
        return false;
    }

    /**
     * @param $lockKey
     * @param $requestId
     * @param $expireTime
     * @return bool
     */
    //错误示例2
    public function lock2($lockKey, $requestId, $expireTime)
    {
        $expire = microtime(true) + $expireTime;
        //如果当前锁不存在返回加锁成功
        $setNx = \Redis::setnx($lockKey, microtime(true));
        if ($setNx) {
            return true;
        }
        //如果锁存在,获取锁的过期时间
        $currentExpire = \Redis::get($lockKey);
        if ($currentExpire && $currentExpire < microtime(true)) {
            //说明锁已经过期,获取上一个锁的过期时间,并设置现在的锁的过期时间
            $oldExpires = \Redis::getSet($lockKey, $expire);
            if ($oldExpires && $oldExpires == $currentExpire) {
                //考虑到多线程并发,只有一个线程的设置值和当前值相同才有权利加锁
                return true;
            }
        }
        //其他情况,一律加锁失败
        return false;
        //由于是客户端自己生成过期时间,所以需要强制要求分布式下每个客户端的时间必须同步;
        //当锁过期的时候,如果多个客户端同时执行 getset 方法,那么虽然最终只有一个客户端可以加锁,但是这个客户端的锁的过期时间可能被其他客户端覆盖;
        //锁不具备拥有者标识,即任何客户端都可以解锁(即A线程抢的了锁,但是执行速度较慢,锁已经过期.此时线程B抢得了锁.B执行过程中,A线程执行完毕,释放了锁,所以会造成锁混乱)
        //因此此锁安全性没法保证,不满足设计原则第一条
    }

    public function lock3($lockKey, $requestId, $expireTime)
    {
        $set = \Redis::set($lockKey, $requestId, 'PX', $expireTime, 'NX');
        if ($set) {
            return true;
        }
        return false;
    }

    public function delLock($lockKey, $requestId)
    {
        $luaScript = <<<EOF
            if redis.call("get",KEYS[1]) == ARGV[1]
            then
                return redis.call("del",KEYS[1])
            else
                return 0
            end
EOF;
        // 利用lua脚本,保证原子性
        $res = \Redis::eval($luaScript, 1, $lockKey, $requestId);
        if ($res) {
            return true;
        }
        return false;
    }
}

调用示例

//使用实例
$distributedLock = new DistributedLock();
$distributedLock->lock3("test", "test", 1000);
if (!$distributedLock) {
    return "抢占锁失败";
}
try {
    //todo 业务逻辑处理
} catch (\Exception $e) {
    //todo 异常处理
} finally {
    $distributedLock->delLock("test","test");
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值