关于redis分布式锁入门

原理 单一实例下:使用setnx命令(在key不存在时,创建并设置value 返回1,key存在时,会返回0)来获取锁。 应用 以扣库存为例,使用锁能够有效保证不会被超卖; 基础版方案:数据库锁 SELECT stock from goods where ID =1 for update; update goods set stock=stock-1 where id=1 and stock=10 // 正常情况下看你业务需求,如果能够在落库前确认库存的话,上面这句话后面的stock=10能...

关于redis分布式锁入门

原理

单一实例下:使用setnx命令(在key不存在时,创建并设置value 返回1,key存在时,会返回0)来获取锁。

应用

以扣库存为例,使用锁能够有效保证不会被超卖;

  • 基础版方案:数据库锁

    SELECT stock from goods where ID =1 for update;
    update  goods set stock=stock-1 where id=1 and stock=10
    // 正常情况下看你业务需求,如果能够在落库前确认库存的话,上面这句话后面的stock=10能够限定库存锁定值
    // 如果不使用这个stock=10的话,会有一定的副作用,比如说常见的,多线程轮扣
    
  • redis 版v0

​ 使用setnx写入一个键值,如果成功则表明获取到锁,执行业务;对于没有获取到锁的,则会轮询获取。

            int waitIntervalMs = 50;//间隔等待时长 毫秒
            string lockKey = "lock_key:" + key; 
            DateTime begin = DateTime.Now;
            while (true)
            {
                if (redisClient.SetNX(lockKey, new byte[] { 1 }) == 1)
                {
                    redisClient.Expire(lockKey, expirySeconds);
                    return true;
                } 
                //不等待锁则返回
                if (waitSeconds <= 0)
                    break;

                if ((DateTime.Now - begin).TotalSeconds >= waitSeconds)//等待超时
                    break;

                System.Threading.Thread.Sleep(waitIntervalMs);
            }
            return false;

不管是该业务成功、失败或者异常,需要确保该锁在周期结束后能被释放

  • redis 版v1

    V0版本存在的缺陷:比较致命的是可能会出现,任务还未完成,锁就超时了,导致key被回收了,那么能不能将锁的申请时间作为值进行存储呢?

    ​```
    int waitIntervalMs = 200;//间隔等待时长 毫秒 设置成足够长,至少要超过数据库的超时响应时间
    string lockKey = "lock_key:" + key; 
    DateTime begin = DateTime.Now;
    DateTime now = DateTime.Now;
    while (true)
                {
                  //在key值存在的情况下
                   if(redisclient.ExistKey(lockKey))
                   {
                   //如果key对应的value值+超时时间后小于当前时间,则移除key值,让业务正常运行
                   if(Convert.ToDateTime(redisclient.GetKey(lockkey).Value)+TimeLimit<now){
                   redisclient.remove(lockkey);
                   }
                   }
                    if (redisClient.SetNX(lockKey, now.tostring()) == 1)
                    {
                        redisClient.Expire(lockKey, expirySeconds);
                        return true;
                    } 
                    //不等待锁则返回
                    if (waitSeconds <= 0)
                        break;
    
                    if ((DateTime.Now - begin).TotalSeconds >= waitSeconds)//等待超时
                        break;
    
                    System.Threading.Thread.Sleep(waitIntervalMs);
                
                
                }
                return false; 
    

    遗留的问题

    在单个redis实例下,上述代码工作都ok,如果存在redis集群,且master挂掉的情况,那么这个锁一定锁不住