上个文章 《.Net Redis 实现分布式锁 》发出去之后,有很多大佬就有了自己的见解,让我获益良多,又学到了一些新姿势(知识)。
上篇主要是采用了 StackExchange.Redis 的两种方式来获取锁.
获取锁
-
1. IDatabase.StringSet(key, value, TimeSpan, When.NotExists,CommandFlags.DemandMaster);(可以补个最后的参数,只有主服务有效)
-
2. IDatabase.LockTake(key, value, TimeSpan, CommandFlags.DemandMaster);
其实,获取锁这部分,是没有啥子大问题的(其实这两个操作的内部代码是一样的),有问题的应该是,解锁的部分。
解锁
关于解锁部分
我写的大概逻辑如下,如果取得了锁,就直接删除锁的key,如果我没有获取到锁(异常或者过期了),那么,我查一下此锁存在不,如果存在,跟我当前一样,我就删除(扫尾工作)。
/// <summary>
/// 释放锁
/// </summary>
/// <returns></returns>
private bool UnLock(bool lockState)
{
if (lockState)
{
IDatabase.KeyDelete(key);
}
else
{
var data = IDatabase.StringGet(key);
if (data == value)
{
IDatabase.KeyDelete(key);
}
}
return true;
}
问题
-
1. 如果正常业务的过期还在执行,第二个获取锁的任务就会获取锁,这个时候,只需要超时时间是合理的,或者执行期间,正常续时,也能解决问题,类似看门狗。
-
2. 就是删除的时候,我实现获取了key里面的值来对比,是否是相同的值,如果相同才会删除,假设此方法并发,则会出现,互相干扰的情况,可能查出来有,删的时候,就已经被另外一个删掉了,这个只能通过原子性操作来实现。
StackExchange LockRelease 的实现
为啥把它提出来,因为它是通过事务实现的,我想这也是解决原子性的一种可行方案,毕竟事务本身就包含了原子性。
以下是它的实现源码部分
public bool LockRelease(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{
if (value.IsNull) throw new ArgumentNullException(nameof(value));
var tran = GetLockReleaseTransaction(key, value);
if (tran != null) return tran.Execute(flags);
// without transactions (twemproxy etc), we can't enforce the "value" part
return KeyDelete(key, flags);
}
private ITransaction? GetLockReleaseTransaction(RedisKey key, RedisValue value)
{
var tran = CreateTransactionIfAvailable(asyncState);
if (tran is not null)
{
tran.AddCondition(Condition.StringEqual(key, value));
tran.KeyDeleteAsync(key, CommandFlags.FireAndForget);
}
return tran;
}
原子性操作问题。
其实就是让解锁的时候,查询key value和删除key value 原子操作,不分开。
业界现有的标准做法都是通过Lua来实现的
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
KEYS[1] 就是 key, ARGV[1] 就是 value。
这样就实现了原子性的解锁操作。
Redis 分布式锁 实现的库
其实,实现的库还挺多的,主要是根据官网文档介绍的Redlock 算法来的,我想这样标准的锁的算法逻辑和库都有了,我们就可以不用自己封了。直接用。
具体Redlock 文档参考 :
https://redis.io/docs/reference/patterns/distributed-locks/
可以看Redis官方对Redis分布式锁的详解,以及各种库的支持。
可以看到C# Redlock的库有
-
1. Redlock-cs
-
2. RedLock.net
-
3. ScarletLock
-
4. Redlock4Net
其中 RedLock.Net 可以来做一个案例
案例实现
安装 nuget包
Install-Package RedLock.net -Version 2.3.2
代码如下:
class Program
{
static void Main(string[] args)
{
Console.Title = "redis redlock Demo by 蓝创精英团队";
var connMultiplexer = ConnectionMultiplexer.Connect("127.0.0.1");
Console.WriteLine($"redis连接状态:{connMultiplexer.IsConnected}");
var multiplexers = new List<RedLockMultiplexer> { connMultiplexer };
using var redlockFactory = RedLockFactory.Create(multiplexers);
Test(redlockFactory);
Console.WriteLine("redis redlock 案例!");
Console.ReadLine();
}
public static void Test(RedLockFactory RedLockFactory)
{
var tasks = new List<Task>();
for (int i = 0; i < 5; i++)
{
tasks.Add(Task.Run(() =>
{
var ID = Guid.NewGuid().ToString("N");
using var redislock = RedLockFactory.CreateLock("key", TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15 * 10), TimeSpan.FromSeconds(1));
if (redislock.IsAcquired)
{
Console.WriteLine($"{DateTime.Now} - {ID}:申请到标志位!");
Console.WriteLine($"{DateTime.Now} - {ID}:处理自己的 事情!");
Thread.Sleep(5 * 1000);
Console.WriteLine($"{DateTime.Now} - {ID}:处理完毕!");
}
}));
}
Task.WaitAll(tasks.ToArray());
}
}
可以看到,封装的更爽了,自动释放锁。
![null C# Redis 分布式锁 续集(新增Mysql分布式锁[还有更多种姿势])](/file/202206/c6682789e5569e95f7d67db4266389ab.png)
效果一致,也挺好用的。
MySql 分布式锁
很多时候,可能服务没有redis服务,那我能不能直接通过mysql实现锁来用。
很高兴的是,我刚好发现一个强大的库,包含了所有锁的实现,爽歪歪,我们赶紧用起来。
这个库就是.Net 的 DistributedLock 库。
支持以下中间件的库
-
1. SqlServer
-
2. Postgres
-
3. MySql
-
4. Oracle
-
5. Redis
-
6. Azure
-
7. ZooKeeper
-
8. FileSystem
-
9. WaitHandles uses operating system global WaitHandles (Windows only)
支持这么多,也支持redis,其实它自己也实现了Redlock 分布式锁。
那就来一个mysql的分布式锁试试。
DistributedLock mysql
Install-Package DistributedLock -Version 2.3.0
代码如下:
static void Main(string[] args)
{
Console.Title = "MySql Lock Demo by 蓝创精英团队";
Test();
Console.WriteLine("MySql Lock 案例!");
Console.ReadLine();
}
public static void Test()
{
var tasks = new List<Task>();
for (int i = 0; i < 5; i++)
{
tasks.Add(Task.Run(async () =>
{
var ID = Guid.NewGuid().ToString("N");
var @lock = new MySqlDistributedLock("key", "Server=127.0.0.1;Port=3306;Database=test;Uid=root;Pwd=123456;");
await using (await @lock.AcquireAsync(TimeSpan.FromSeconds(15 * 10)))
{
Console.WriteLine($"{DateTime.Now} - {ID}:申请到标志位!");
Console.WriteLine($"{DateTime.Now} - {ID}:处理自己的 事情!");
Thread.Sleep(5 * 1000);
Console.WriteLine($"{DateTime.Now} - {ID}:处理完毕!");
}
}));
}
Task.WaitAll(tasks.ToArray());
}
效果还是很不错的,跟上边一样的
![null C# Redis 分布式锁 续集(新增Mysql分布式锁[还有更多种姿势])](/file/202206/e67c928137e643a0bced186713f427fc.png)
总结
学习东西就是否决自己,然后重新认识自己的过程,认知的壁垒不是那么容易打破的,好奇心是一把能打破它的钥匙。
代码地址
https://github.com/kesshei/RedlockDemo.git
https://gitee.com/kesshei/RedlockDemo.git