redis锁的进化历程

      日常工作中总是会有高并发的场景,需要实现锁机制来保证序列性,接下来我们一步一步实现一个 单机Redis下基本可靠的Redis锁(ps: 如果是Redis集群的话,就存在主从切换锁失效的问题,解决这个问题的话就比较麻烦了,这里不做讨论,现有的解决方案有redlock,大家可以看下它的实现原理)

Redis锁 第一版(php实现):

    //加锁
    public function lock($key)
    {
        $redisConnect = Redis::connection();
        $v = $redisConnect->get($key);
        if ($v == 1) {
            return false;
        }
        $res = $redisConnect->setex($key);
        return (bool)$res;
    }

    //解锁
    public function unlock($key)
    {
        $redisConnect = Redis::connection();
        return $redisConnect->del($key);
    }

版本一的问题:如果加锁之后进程中断,就会死锁

版本二,设置锁的超时时间来解决版本一问题

    public function lock($key, $seconds)
    {
        $redisConnect = Redis::connection();
        $v = $redisConnect->get($key);
        if ($v == 1) {
            return false;
        }
        $res = $redisConnect->setex($key, $seconds, 1);
        return (bool)$res;
    }

    //解锁
    public function unlock($key)
    {
        $redisConnect = Redis::connection();
        return $redisConnect->del($key);
    }

第二版的问题:设置了超时时间,但是有一种场景,a加锁之后到了超时时间a还没执行完,这个时候锁失效 b可以再加锁,然后a执行完毕 就会把b加的锁删除。 说到底问题出在了,不同的进程加的锁是没区别的。

版本三: 解决版本二的问题 可以在设置锁的时候 加入一个唯一置,在删除锁的时候,如果发现唯一值已经不是设置的了,就不删除改锁了。这里有一个问题就是删除的时候需要判断是否是唯一值,然后再决定删除与否,这个动作本身就不是原子操作的,所以要封账一个lua命令来实现原子性。

    public function lock($key, $value,  $seconds)
    {
        $redisConnect = Redis::connection();
        $res =  $redisConnect->eval(
            $this->setnxex(), $key, $value, $seconds
        );
        return (bool)$res;
    }

    /**
     * 解锁
     *
     * @param string $key
     * @return bool
     */
    public function unlock($key, $value)
    {
        $redisConnect = Redis::connection();
        return $redisConnect->eval(
            $this->delIfExist(), $key, $value
        );
    }

    public function delIfExist()
    {
        return <<<'LUA'
local job = redis.call('get', KEYS[1])
local iseq = false
if(job == KEYS[2]) then
    redis.call('del', KEYS[1])
    iseq = true
end
return iseq
LUA;
    }

    public function setnxex($key, $value, $exp)
    {
        return <<<'LUA'
local job = redis.call('get', KEYS[1])
local iseq = false
if(job == nil) then
    redis.call('setex', KEYS[1], ARGV[1], KEYS[2])
    iseq = true
end
return iseq
LUA;
    }

    public function main()
    {
        $uniqid = uniqid();
        $key = "buy_goods_unique_key";
        $res = this.lock($key, $uniqid, 3);
        if (!$res) {
            throw new Exception("稍后重试");
        }
        //buy goods
        $this->unlock($key, $uniqid);
    }

这里还是没有完全解决问题,因为a事务执行超时的过程中,锁超时后被b拿到,那么整个流程b是不知道和a并发了的,还是没有做到百分百的完备。

原文地址:https://www.cnblogs.com/tobemaster/p/11350892.html