• C# Redis 分布式锁 续集 (新增Mysql分布式锁[还有更多种姿势])


    上个文章 《.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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    问题

    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;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    原子性操作问题。

    其实就是让解锁的时候,查询key value和删除key value 原子操作,不分开。

    业界现有的标准做法都是通过Lua来实现的

    if redis.call('get', KEYS[1]) == ARGV[1] then
    	return redis.call('del', KEYS[1])
    else
    	return 0
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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
    
    • 1

    代码如下:

    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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    可以看到,封装的更爽了,自动释放锁。

    效果一致,也挺好用的。

    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
    
    • 1

    代码如下:

    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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    效果还是很不错的,跟上边一样的

    总结

    学习东西就是否决自己,然后重新认识自己的过程,认知的壁垒不是那么容易打破的,好奇心是一把能打破它的钥匙。

    代码地址

    https://github.com/kesshei/RedlockDemo.git

    https://gitee.com/kesshei/RedlockDemo.git

    一键三连呦!,感谢大佬的支持,您的支持就是我的动力!

  • 相关阅读:
    基于matlab寻找并显示一维数组t中的素数
    区块链积分系统:革新支付安全与用户体验的未来
    Java————网络编程
    Java版 招投标系统简介 招投标系统源码 java招投标系统 招投标系统功能设计
    分布式系统的发展历程
    软件加密系统Themida应用程序保护指南(三):保护宏的选择
    【数据结构与算法(C语言)】离散事件模拟- 单链表和队列的混合实际应用
    springboot集成Quartz定时任务组件
    Linux 学习笔记(5-6)
    深度学习Course5第三周Sequence Models & Attention Mechanism习题整理
  • 原文地址:https://blog.csdn.net/kesshei/article/details/125453061