上个文章 《.Net Redis 实现分布式锁 》发出去之后,有很多大佬就有了自己的见解,让我获益良多,又学到了一些新姿势(知识)。
上篇主要是采用了 StackExchange.Redis 的两种方式来获取锁
其实,获取锁这部分,是没有啥子大问题的(其实这两个操作的内部代码是一样的),有问题的应该是,解锁的部分。
关于解锁部分
我写的大概逻辑如下,如果取得了锁,就直接删除锁的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;
}
为啥把它提出来,因为它是通过事务实现的,我想这也是解决原子性的一种可行方案,毕竟事务本身就包含了原子性。
以下是它的实现源码部分
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。
这样就实现了原子性的解锁操作。
其实,实现的库还挺多的,主要是根据官网文档锁介绍的Redlock 算法来的,我想这样标准的锁的算法逻辑和库都有了,我们就可以不用自己封了。直接用。
具体Redlock 文档参考 :
https://redis.io/docs/reference/patterns/distributed-locks/
可以看Redis官方对Redis分布式锁的详解,以及各种库的支持。
可以看到C# Redlock的库有
其中 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());
}
}
可以看到,封装的更爽了,自动释放锁。
效果一致,也挺好用的。
很多时候,可能服务没有redis服务,那我能不能直接通过mysql实现锁来用。
很高兴的是,我刚好发现一个强大的库,包含了所有锁的实现,爽歪歪,我们赶紧用起来。
这个库就是.Net 的 DistributedLock 库。
支持以下中间件的库
支持这么多,也支持redis,其实它自己也实现了Redlock 分布式锁。
那就来一个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());
}
效果还是很不错的,跟上边一样的
学习东西就是否决自己,然后重新认识自己的过程,认知的壁垒不是那么容易打破的,好奇心是一把能打破它的钥匙。
https://github.com/kesshei/RedlockDemo.git
https://gitee.com/kesshei/RedlockDemo.git
一键三连呦!,感谢大佬的支持,您的支持就是我的动力!