• 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并发了的,还是没有做到百分百的完备。

  • 相关阅读:
    nfs目录权限
    14.5.5 AUTO_INCREMENT Handling in InnoDB 在InnoDB AUTO_INCREMENT处理
    Tk 表格的宽度
    化工企业数据分析平台项目之应收款分析
    化工企业数据分析平台项目之应收款分析
    14.5.3 Grouping DML Operations with Transactions 分组DML 事务操作
    perl | 匹配多个
    struts的控制器组件
    解决Thinkpad开启飞行模式无法连接无线网络
    如何解决Thinkpad连接wifi经常断线
  • 原文地址:https://www.cnblogs.com/tobemaster/p/11350892.html
Copyright © 2020-2023  润新知