zoukankan      html  css  js  c++  java
  • 基于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

  • 相关阅读:
    LeetCode OJ 112. Path Sum
    LeetCode OJ 226. Invert Binary Tree
    LeetCode OJ 100. Same Tree
    LeetCode OJ 104. Maximum Depth of Binary Tree
    LeetCode OJ 111. Minimum Depth of Binary Tree
    LeetCode OJ 110. Balanced Binary Tree
    apache-jmeter-3.1的简单压力测试使用方法(下载和安装)
    JMeter入门教程
    CentOS6(CentOS7)设置静态IP 并且 能够上网
    分享好文:分享我在阿里8年,是如何一步一步走向架构师的
  • 原文地址:https://www.cnblogs.com/Burt/p/6656383.html
Copyright © 2011-2022 走看看