基于Redis的分布式锁

      分布式锁可以基于很多种方式实现,比如zookeeper、redis...。基本原理用一个状态值表示锁,对锁的占用和释放通过状态值来标识。

     上次搞了zookeeper,今天把redis的也给补上,demo写完好一阵了,一直没发出来,当时写完也没测试,今天测试测试修补修补就给放出来,大多也是参照java的版本去仿着改成c#版本的,感觉原理都是相同的,就是语法啥的有些不同而已,用的还是StackExchange.Redis。(demo见结尾)

      1.redis的锁主要基于setNXgetSET命令的

          1.1 setNX(SET if Not eXists),字面意思(如果不存在,则 SET),如果存在,那就不SET了。

                 命令:SETNX key value   返回值:写成功 1 ,不成功 0  (是不是相当好判断)

          1.2  GETSET ,将给定 key 的值设为 value ,并返回 key 的旧值(old value)。

                 命令:GETSET key value 返回值:写成功,返回给定 key 的旧值,当 key 没有旧值时,也即是, key 不存在时,返回 nil 。

       2.了解完这两个命令,可以把思路先理一下!

             比如我们要对某个商品进行加锁操作,暂定为 product112233 112233为商品编号(<current Unix time + lock timeout + 1>  为过期时间)。

             步骤1.   首先要操作啦,先加锁

                      执行命令SETNX   product112233   <current Unix time + lock timeout + 1>  

                      返回1 加锁成功 继续业务...... 完成后解锁(解锁最后再说)

                      返回0 加锁失败

              步骤2.  上面加锁失败了,看来是已经有人在占用112233的资源,那我们看看它的过期时间

                      执行命令Get product112233  返回了过期时间 这时候对比下当前时间 看看,无非也就两种结果

                      过期了 再加锁 come on

                               执行命令 GetSet product112233   <current Unix time + lock timeout + 1> 

                               返回 oldValue   对比下 oldValue  

                                     是你set的Value     恭喜加锁成功  继续业务...... 完成后解锁(解锁最后再说)

                                     不是你set的Value  加锁又没成功 跟没过期一样 反正就是没获取锁

                       没过期 

                                一定时间间隔再去尝试加锁(就是上面步骤再来一次),总体时间限制有一个限定,超过时间锁还没拿到那就没办法了,提示加锁失败......

              步骤3. 释放锁 释放锁就是删除锁

                       删除锁DEL,需要先判断锁是否已超时,如果锁已超时,那锁有可能被其他进程获得了,也就不属于你了,这时候直接del,你把别人的锁给del了,那可就不太好了

     3.ok,整体思路就表述完了,下面给出代码实现。

               简单贴一下关于操作redis的几个Api

        /// <summary>
        /// 操作在设置键的值的同时,还会返回键的旧值
        /// </summary>
        /// <param name="key">键值</param>
        /// <param name="val">新值</param>
        /// <returns>旧值</returns>
        public RedisValue StringGetSet(string key, string val)
        {
            key = AddSysCustomKey(key);
            return Do(db => db.StringGetSet(key, val));
        }
        /// <summary>
        /// SET if Not eXists 将 key 的值设为 value,当且仅当 key 不存在 
        /// 若给定的 key 已经存在,则 SETNX 不做任何动作。
        /// </summary>
        /// <param name="key"></param>
        /// <param name="val"></param>
        /// <returns>- 1,当 key 的值被设置 - 0,当 key 的值没被设置</returns>
        public string StringSETNX(string key, string val)
        {
            key = AddSysCustomKey(key);
            RedisResult RResult = Do(db => db.ScriptEvaluate(
               LuaScript.Prepare(" local lockSet = redis.call('setnx', @key, @val)   return lockSet"), new { key, val }));
            return RResult.ToString();
        }
        /// <summary>
        /// 执行lua脚本
        /// </summary>
        /// <param name="luascript"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public string ScriptEvaluate(string luascript, object parameters)
        {
            RedisResult RResult = Do(db => db.ScriptEvaluate(
               LuaScript.Prepare(luascript), parameters));
            return RResult.ToString();
        }

               关于操作锁的几个action

 /// <summary>
        ///  获取锁
        /// </summary>
        /// <param name="lockkey">锁的key值</param>
        /// <param name="expireMsecs">锁到期时间</param>
        /// <returns></returns>
        public bool tryGetLock(string lockkey, int expireMsecs)
        {
            try
            {
                int timeout = _timeoutMsecs;
                while (timeout >= 0)
                {
                    long expires = DateTime.Now.DateTimeToUnixTimestamp() + expireMsecs + 1;
                    String expiresStr = expires.ToString(); //锁到期时间
                    RedisHelper Redis = new RedisHelper(0);
                    if (Redis.StringSETNX(lockkey, expiresStr) == "1")
                    {
                        return true;
                    }
                    String currentValueStr = Redis.StringGet(lockkey); //redis里的时间
                    if (currentValueStr != null && long.Parse(currentValueStr) < DateTime.Now.DateTimeToUnixTimestamp())
                    {
                        //判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
                        String oldValueStr = Redis.StringGetSet(lockkey, expiresStr);
                        //获取上一个锁到期时间,并设置现在的锁到期时间,
                        //只有一个线程才能获取上一个线上的设置时间,因为getSet是同步的
                        if (oldValueStr != null && oldValueStr.Equals(currentValueStr))
                        {
                            //如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁
                            return true;
                        }
                    }
                    timeout -= 100;
                    Thread.Sleep(100);
                }
            }
            catch (Exception)
            {
                unlock(lockkey);
            }
            return false;
        }

        /// <summary>
        /// 解除锁占用
        /// </summary>
        /// <param name="lockkey"></param>

        public void unlock(string lockkey)
        {
            //在进程释放锁,即执行 DEL lock.foo 操作前,需要先判断锁是否已超时。如果锁已超时,
            //那么锁可能已由其他进程获得,这时直接执行 DEL lock.foo 操作会导致把其他进程已获得的锁释放掉。
            RedisHelper Redis = new RedisHelper(0);
            String currentValueStr = Redis.StringGet(lockkey); //redis里的时间
            if (currentValueStr != null && long.Parse(currentValueStr) < DateTime.Now.DateTimeToUnixTimestamp())
            {
                Redis.KeyDelete(lockkey);
            }
        }
View Code

              测试用了两个demo

                      

      最后的测试结果就不贴了,自己想知道的下demo测试测试吧哈!

      要开始清明假期了,大家清明节快乐哈!  

      demo下载:http://pan.baidu.com/s/1nvz6Si9

原文地址:https://www.cnblogs.com/Burt/p/6656383.html