FreeRedis分布式锁实现以及使用

今日上班听到同事在准备面试题分布式锁(准备溜溜球),随即加入了群聊复习了一波,于是有了这篇小作文。

场景

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

无锁裸奔表现

示例代码:

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

/// <summary> /// 模拟库存服务 /// </summary> public class StockService { private static RedisClient cli = new RedisClient("127.0.0.1:6379"); /// <summary> /// 减库存操作 /// </summary> /// <param>商品数</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); }); }); }

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

FreeRedis分布式锁实现以及使用

我们的库存只有 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>商品数</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; } }

执行结果如下所示:

FreeRedis分布式锁实现以及使用

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

看看 FreeRedis 实现的分布式锁

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

上锁

FreeRedis分布式锁实现以及使用

循环检测获取锁操作是否过期,过期直接返回 Null, 否则继续步骤二

SetNx 设置值,如果成功,创建分布式锁对象,否则线程等待一会,继续第一步,如此循环

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

比如说现在 请求1,请求2,请求3 同时过来,请求1 先抢到了锁,开始执行。

但是 请求1 的业务执行时间比较长,锁已经过期失效了,业务还没有执行完成。这时 请求2 获取到锁,执行自己的业务。就出现了 请求1 和 请求2 并发执行了

当 请求1 执行完自己的业务的时候,执行解锁操作,因为键值都一样,会误把 请求2 的锁给释放掉,导致故障

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

看门狗机制

FreeRedis分布式锁实现以及使用


FreeRedis分布式锁实现以及使用

定时执行 Refresh 方法

通过 lua 脚本设置新的过期时间,不成功的话(已解锁),删除定时器

解锁

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpwyyp.html