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

  • 相关阅读:
    scapy--初识
    python--re(匹配字符串)
    python--pdb
    Fedora 配置
    Ubuntu 18.04 配置
    python--git
    python--os
    day28 Pyhton 面向对象 继承
    day28 Pyhton MRO和C3算法
    数学知识回顾02
  • 原文地址:https://www.cnblogs.com/Burt/p/6656383.html
Copyright © 2011-2022 走看看