zoukankan      html  css  js  c++  java
  • 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并发了的,还是没有做到百分百的完备。

  • 相关阅读:
    图文详解 Android Binder跨进程通信机制 原理
    支链氨基酸怎么吃
    C#泛型约束
    树状结构 Tree data structure in C#
    wrap ConcurrentDictionary in BlockingCollection
    ConcurrentBag扩展 批量加入
    Dictionary GetOrAdd
    ConcurrentDictionary AddOrUpdate
    object pool
    C# 结构体定义 转换字节数组 z
  • 原文地址:https://www.cnblogs.com/tobemaster/p/11350892.html
Copyright © 2011-2022 走看看