Redis 分布式锁

实现一个分布式锁定,我们至少要考虑它能满足一下的这些需求:

  • 互斥,就是要在任何的时刻,同一个锁只能够有一个客户端用户锁定.
  • 不会死锁,就算持有锁的客户端在持有期间崩溃了,但是也不会影响后续的客户端加锁
  • 谁加锁谁解锁,很好理解,加锁和解锁的必须是同一个客户端

Redis 分布式锁

这里使用PRedis来访问Redis

<?php

use PRedis;

class RedisLock 
{

    protected is_block = 1; //获取阻塞获取锁

    protected $_redis = null;

    /**
     * 当前请求id
     * @var integer
     */
    protected static $request_id = '';

    /**
     * 锁过期时间
     * 单位:秒
     * @var integer
     */
    protected $expiredTime = 30;

    /**
     * 获取阻塞锁最长等待时间
     * 不宜过长,请考虑实际情况单次锁释放时间设置
     * 高并发情况,较长阻塞时间会造成大量进程堆积
     * 单位:秒
     * @var integer
     */
    protected $waitTime = 10;

    /**
     * 阻塞锁重试频率
     * 每次请求的间隔时间
     * 单位: 微秒
     * @var integer
     */
    protected $frequency = 200;


    public function __construct()
    {
        $this->_redis = PRedis::connection('lock');
    }

    public function lock($name, $policy=0, $config=[])
    {
        $req_id = $this->getRequestId();

        if ($policy === self::BLOCKED) {
            $result = 'false';
            $beginTime = time();
            while(true) {
                $result = $this->_redis->set($name, $req_id, 'PX', $this->expiredTime * 1000, 'NX');
                if ('OK' === (string) $result) {
                    return true;
                } elseif (time()-$beginTime<=$this->waitTime) {
                    usleep($this->frequency * 1000);
                } else {
                    return false;
                }
            }

            return 'OK' === (string) $result;
        } else {
            $result = $this->_redis->set($name, $req_id, 'PX', $this->expiredTime * 1000, 'NX');
            return 'OK' === (string) $result;
        }

        return false;
    }

    /**
     * 使用lua 保证原子操作,客户端 A 加锁成功后一段时间再来解锁,在执行删除 del 操作的时候锁过期了,而且这时候又有其他客户端 B 来加锁 (这时候加锁是肯定成功的,因为客户端 A 的锁过期了), 这时客户端 A 再执行删除 del 操作,会把客户端 B 的锁给清了.
     */
    public function unlock($name)
    {
        $lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        $req_id = $this->getRequestId();
        $result = $this->_redis->eval($lua, 1, $name, $req_id);

        return (int) $result === 1;
    }

    /**
     * @return $request_id 最好保证唯一值
     */
    protected function getRequestId()
    {
        $request_id = static::$request_id ?: (static::$request_id = uniqid());

        return $request_id . getmypid();
    }
}

$this->_redis->set($name, $req_id, ‘PX’, $this->expiredTime * 1000, ‘NX’);

  • 第一个 name 是锁的名字,这个由具体业务逻辑控制,保证唯一即可,比如并发更新一个 sku的库存的时候 SKU0001就可以加上锁以免超卖
  • 第二个是请求 ID,这样做的目的主要是为了保证加解锁的唯一性。这样我们就可以知道该锁是哪个客户端加的.
  • 第三个参数是一个标识符,标识时间戳以毫秒为最小单位
  • 具体的过期时间
  • 这个参数是 NX, 表示当 key 不存在时我们才进行 set 操作,这样锁就不会形成覆盖。
  • 分布式唯一ID使用的snowflack这个是编译安装,还有用swoole_lock实现的扩展包

  转载请注明: WYQ Redis 分布式锁

 上一篇
延迟队列 延迟队列
比如要实现30分钟未支付订单取消,量少的时候可以用数据库轮训的方式,但是数据量大的话,轮训的并发和准确性就不可靠,这个时候可以用延迟队列来解决这个问题 延迟队列的实现 [RabbitMQ] RabbitMQ通过RabbitMQ Delaye
2019-01-01
下一篇 
Pypppeteer Pypppeteer
之前做爬虫或者浏览器自动化操作都用selenium ,再配合docker可以进行分布式部署,但是selenium太耗性能,这里有另外个选择puppeteer谷歌浏览器在17年自行开发了Chrome Headless特性,并与之同时推出了pu
2018-06-21
  目录