前言
基于Redis的分布式锁实现,原理很简单嘛:检测一下Key是否存在,不存在则Set Key,加锁成功,存在则加锁失败。对吗?这么简单吗?
如果你真这么想,那么你真的需要好好听我讲一下了。接下来,咱们找个例子研究一下。
在开始之前,咱们先定些规则:
- 关于示例代码:
- 需要搭配我准备的示例代码,该示例采用C#编写
- 示例中的材料Id固定为10000
- 示例中的材料初始库存均为100
- 关于Redis中的Key:
- 指示材料库存的Key为
ProductStock_10000
- 自己实现的分布式锁中,指示锁的Key为
DistributedLock_10000
- RedLock.net中,指示锁的Key为
redlock:10000
- 指示材料库存的Key为
1、假如没有锁
如果没有锁,我们可以通过Jmeter并发100个请求,看看最后库存是不是0
/// <summary>
/// 无锁扣减库存
/// </summary>
/// <returns></returns>
[HttpPost("DecreaseProductStockWithNoLock")]
public async Task<string> DecreaseProductStockWithNoLock()
{
var stockKey = GetProductStockKey(ProductId);
var currentQuantity = (long)(await _redisDatabase.Database.StringGetAsync(stockKey));
if (currentQuantity < 1)
throw new Exception("库存不足");
var leftQuantity = currentQuantity - 1;
await _redisDatabase.Database.StringSetAsync(stockKey, leftQuantity);
return $"剩余库存:{leftQuantity}";
}
完了,库存全乱了,收拾收拾,跑路吧o(╥﹏╥)o!
2、单应用中的锁
提到锁,大多数人首先想到的应该就是Monitor
的语法糖lock
了,这是大多数人最先接触到的一种锁。在单应用中,因为lock是线程锁,所以使用该锁一般是没有什么问题的。
/// <summary>
/// 在单应用中扣减库存
/// </summary>
/// <returns></returns>
[HttpPost("DecreaseProductStockInSingleApp")]
public string DecreaseProductStockInSingleApp()
{
long leftQuantity;
lock (_lockObj)
{
var stockKey = GetProductStockKey(ProductId);
var currentQuantity = (long)_redisDatabase.Database.StringGet(stockKey);
if (currentQuantity < 1)
throw new Exception("库存不足");
leftQuantity = currentQuantity - 1;
_redisDatabase.Database.StringSet(stockKey, leftQuantity);
}
return $"剩余库存:{leftQuantity}";
}
结果和我们所期望的一样,剩余库存为0
但是如果我们进行应用集群,部署多份一模一样的应用,那lock
就无能为力了。接下来,咱们启动两个应用实例来看看
# 以开发环境运行,能看到更多信息
dotnet XXTk.Redis.DistributedLock.Api.dll --urls http://localhost:5000 --environment Development
dotnet XXTk.Redis.DistributedLock.Api.dll --urls http://localhost:5010 --environment Development
可见,一共发送了100个请求,本应该最后库存为0的,却还剩17个
3、应用集群中的锁
3.1版本1
很明显,lock
已经没用了,是时候进入咱们的主题了——基于Redis的分布式锁设计。
初步的思路是这样的:
- 将材料Id作为Redis Key
- 如果Redis中存在该Key,则认为锁已经被其他线程占用了
- 如果Redis中不存在Key,则将该Key添加到Redis中,Value则随意赋值
- 当获取到锁的业务执行完毕后,将Key从Redis中移除
有了思路,接下来就该想一下如何实现了。很幸运,Redis的命令SETNX key value
完全满足我们的需求,实现如下:
///