PHP+redis实现分布式锁

什么是分布式锁

定义:在分布式环境下,一个共享的可见的公共资源,各个线程通过对这个公共资源的抢占,能够使得一个代码块在同一时间只能被一个机器的一个线程执行,那这个公共资源就是分布式锁,或者说这整个机制就是分布式锁。

或者从使用场景定义:分布式锁主要用于在分布式环境中保护跨进程、跨主机、跨网络的共享资源实现互斥访问,以达到保证数据的一致性

分布式锁实现方式

锁的实现方式有多种,只要能满足所有线程都能看得到这个锁标记即可。

常见的方式是使用数据库、缓存或者zookeeper来实现分布式锁,除了这些,其实一个网络中的共享可见的可读写的资源就可以用作实现锁。

锁的操作主要有两个,即lock()和unlock()。

分布式特性

  • 这把锁要是一把可重入锁 (一般是指设置过期时间,避免死锁)
  • 这把锁最好是一把阻塞锁(阻塞锁是指不断循环请求获取锁资源,一般根据业务需求需不需要阻塞)
  • 有高可用的获取锁和释放锁的功能
  • 获取锁和释放锁的性能要好

PHP+REDIS实现

  • 在多进程或者分布式执行同一份代码去处理同一份业务,在未加锁的情况下,并发下会出现重复执行。
  • 例如:多进程查询数据库库存去处理业务,可能多个任务全部拿到凭证,导致库存变成负数。这种情况下需要引入分布式锁,避免数据库修改未完成比其他任务获取,下面使用php+redis进行实现
<?php

class RedisLock
{
    public $redis;
    public $lockedNames = [];

    public function __construct()
    {
        $this->redis = new \Redis();
        $this->redis->connect('127.0.0.1', '6379');
    }

    public function lock($name, $timeout = 10, $expire = 15, $waitIntervalUs = 100000)
    {
        if ($name == null) return false;
        //获取当前时间
        $now = time();
        //获取锁失败时的等待超时时刻
        $timeoutAt = $now + $timeout;
        //锁的最大生存时刻
        $expireAt = $now + $expire;

        $redisKey = "Lock:{$name}";
        while (true) {
            //将rediskey的最大生存时刻存到redis里,过了这个时刻该锁会被自动释放
            $result = $this->redis->setnx($redisKey, $expireAt);

            if ($result) {
                //设置key的失效时间
                $this->redis->expire($redisKey, $expire);
                //将锁标志放到lockedNames数组里
                $this->lockedNames[$name] = $expireAt;
                //以秒为单位,返回给定key的剩余生存时间
                $ttl = $this->redis->ttl($redisKey);
                //ttl小于0 表示key上没有设置生存时间(key是不会不存在的,因为前面setnx会自动创建)
                //如果出现这种状况,那就是进程的某个实例setnx成功后 crash 导致紧跟着的expire没有被调用
                //这时可以直接设置expire并把锁纳为己用
                if ($ttl < 0) {
                    $this->redis->set($redisKey, $expireAt);
                    $this->lockedNames[$name] = $expireAt;
                    return true;
                }
                return true;
            }
  				/*****检测key是否未设置过期时间*****/
            if ($this->redis->get($redisKey) < $expireAt) {
                //设置key的失效时间
                $this->redis->expire($redisKey, 0);
            }

            /*****循环请求锁部分*****/
            //如果没设置锁失败的等待时间 或者 已超过最大等待时间了,那就退出
            if ($timeout <= 0 || $timeoutAt < microtime(true)) break;
            //隔 $waitIntervalUs 后继续 请求
            usleep($waitIntervalUs);

        }

        return false;
    }

    public function isLocking($name)
    {
        return key_exists($name, $this->lockedNames);
    }

    public function unlock($name)
    {
        //先判断是否存在此锁
        if ($this->isLocking($name)) {
            //删除锁
            if ($this->redis->del("Lock:$name")) {
                //清掉lockedNames里的锁标志
                unset($this->lockedNames[$name]);
                return true;
            }
        }
        return false;
    }
}

分布式锁应用

这里简单使用workerman开启多进程进行使用

<?php
require 'RedisLock.php';

class PageLock
{
    public static function plock($name, $timeout = 10, $exp = 15)
    {
        return RedisLock::onLock($name, function () {
            $aa = time();
            return $aa;
        }, $timeout, $exp);
    }

    public static function run($name)
    {
        $time = self::plock($name);
        sleep(1);
        echo $time . PHP_EOL;
    }
}

在上面的代码中 onLock是获取到锁执行代码然后释放锁,这里只是打印时间,在调用run里面进行睡眠,这里多进程都获取到了锁进行了时间打印
在这里插入图片描述

上面的代码onLock只是简单做了打印时间,咋实际业务中onLock中进行的业务会阻塞,其他进程无法获取到锁,打印的时间将不一样,能够清晰的看到使用锁的过程

在这里插入图片描述

但是这样做,多进程将变得没有意义,加锁只是为了多进程不执行同一个任务,这里需要引入redis incr进行区分性处理,每个进程获取到锁,将redis加1,根据序号执行下面的业务 如
四个进程全部获取到锁,得到的数字是1,2,3,4那么根据需要同步执行不续等待,
如对应数据库id1234,则能同时查询出4条不重复的数据

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值