FreeRedis分布式锁实现以及使用

前言

最近公司的小伙伴在准备面试题,随时准备跑路。听到他们正在讨论分布式锁相关知识,便也立即加入了群聊(我也想溜溜球了)。于是有了今天这篇小作文,记录一下知识点,也希望能帮助其他的小伙伴共同学习,共同进步。

场景

本文中的演示 DEMO, 以下订单减库存为例。

无锁裸奔表现

示例代码:

先来模拟一个库存服务呗!

    /// <summary>
    /// 模拟库存服务
    /// </summary>
    public class StockService
    {
        private static RedisClient cli = new RedisClient("127.0.0.1:6379");

        /// <summary>
        /// 减库存操作
        /// </summary>
        /// <param name="goodsCount">商品数</param>
        /// <returns></returns>
        public bool ReduceStock(int goodsCount)
        {
            var stockCount = cli.Get<int>("StockCount");
            if (stockCount > 0 && stockCount >= goodsCount)
            {
                stockCount -= goodsCount;
                cli.Set("StockCount", stockCount, 10);
                Console.WriteLine($"线程Id:{Thread.CurrentThread.ManagedThreadId},抢购成功!库存数:{stockCount}");
                return true;
            }

            Console.WriteLine($"线程Id:{Thread.CurrentThread.ManagedThreadId},抢购失败!");

            return false;
        }
    }

模拟500个并发请求,开始测试。

        static void Main(string[] args)
        {
            var stockService = new StockService();
            
            // 初始化库存
            var cli = new RedisClient("127.0.0.1:6379");
            cli.Set("StockCount", 10, 10);

            // 模拟 500 个并发
            Parallel.For(0, 500, (i) => { Task.Run(() => { stockService.ReduceStock(1); }); });
        }

执行完成后,结果如下图所示:

我们的库存只有 10 个,截图可见,至少有 29 个请求抢购成功了,出现了超卖的现象。

上分布式锁表现

针对无锁情况下出现的并发问题,如果是单体应用,用 lock 可以解决,但不适用于分布式应用。FreeRedis 中已有现成实现的分布式锁,我们先来看看是如何使用的吧!

修改一下订单服务代码:

    /// <summary>
    /// 模拟库存服务
    /// </summary>
    public class StockService
    {
        private static RedisClient cli = new RedisClient("127.0.0.1:6379");
        private static readonly string _distributedLockKey = "DISTRIBUTEDLOCKKEY";

        /// <summary>
        /// 减库存操作
        /// </summary>
        /// <param name="goodsCount">商品数</param>
        /// <returns></returns>
        public bool ReduceStock(int goodsCount)
        {
            // 取锁
            var lockObj = cli.Lock(_distributedLockKey, 1);
            if (lockObj != null)
            {
                var stockCount = cli.Get<int>("StockCount");
                if (stockCount > 0 && stockCount >= goodsCount)
                {
                    stockCount -= goodsCount;
                    cli.Set("StockCount", stockCount, 10);
                    Console.WriteLine($"线程Id:{Thread.CurrentThread.ManagedThreadId},抢购成功!库存数:{stockCount}");
                    lockObj.Unlock(); // 解锁
                    return true;
                }

                Console.WriteLine($"线程Id:{Thread.CurrentThread.ManagedThreadId},抢购失败!");
                lockObj.Unlock(); // 解锁
            }

            return false;
        }
    }

执行结果如下所示:

从输出结果中可以看出,库存有序的扣除中,确实只有 10 个请求是抢购成功。

看看 FreeRedis 实现的分布式锁

通过上面示例可以看见,分布式锁的使用无非就是 LockUnLock 的操作。我这里直接用编辑器调试进去看了,就不是上 GitHub 上下载代码看了。体验不好,还请担待。

上锁

  1. 循环检测获取锁操作是否过期,过期直接返回 Null, 否则继续步骤二
  2. SetNx 设置值,如果成功,创建分布式锁对象,否则线程等待一会,继续第一步,如此循环

为啥不可以设置唯一值呢?在没有启动自动续时(看门狗机制),业务执行时间超过了锁的过期时间时,会引发问题。

  • 比如说现在 请求1请求2请求3 同时过来,请求1 先抢到了锁,开始执行。
  • 但是 请求1 的业务执行时间比较长,锁已经过期失效了,业务还没有执行完成。这时 请求2 获取到锁,执行自己的业务。就出现了 请求1请求2 并发执行了
  • 请求1 执行完自己的业务的时候,执行解锁操作,因为键值都一样,会误把 请求2 的锁给释放掉,导致故障

通过设置值的唯一,当删除缓存的时候,还需要判断一下值是不是一致,来防止误释放其他锁。

看门狗机制


  1. 定时执行 Refresh 方法
  2. 通过 lua 脚本设置新的过期时间,不成功的话(已解锁),删除定时器

解锁

  1. 通过 lua 脚本匹配 都一样的key, 才能删除

分布式锁的坑参考连接

给TA买糖
共{{data.count}}人
人已赞赏
经验教程

进阶Java多线程

2021-3-10 16:52:00

经验教程

kubernetes生产实践之redis-cluster

2021-3-10 17:23:00

⚠️
免责声明:根据《计算机软件保护条例》第十七条规定“为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。”您需知晓本站所有内容资源均来源于网络,仅供用户交流学习与研究使用,版权归属原版权方所有,版权争议与本站无关,用户本人下载后不能用作商业或非法用途,需在24个小时之内从您的电脑中彻底删除上述内容,否则后果均由用户承担责任;如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途,否则一切后果请您自行承担,如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。 本站为个人博客非盈利性站点,所有软件信息均来自网络,所有资源仅供学习参考研究目的,并不贩卖软件,不存在任何商业目的及用途,网站会员捐赠是您喜欢本站而产生的赞助支持行为,仅为维持服务器的开支与维护,全凭自愿无任何强求。本站部份代码及教程来源于互联网,仅供网友学习交流,若您喜欢本文可附上原文链接随意转载。
无意侵害您的权益,请发送邮件至 momeis6@qq.com 或点击右侧 私信:momeis 反馈,我们将尽快处理。
0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索