c# redis分布式锁

学习资料:

90分钟完全吃透Redis分布式锁!(C#/.NETCore/.NET Core)B0562_哔哩哔哩_bilibili

0-2是概念

3-4是代码演示

1、什么是分布式锁

普通锁机制:

悲观锁和乐观锁

c# redis分布式锁_第1张图片

分布式锁:

为了解决多个进程操作共享资源出现竞争,需要保证数据库的安全性

阻塞锁就是一直等到拿到锁

非阻塞锁就是拿不到锁就不拿了

c# redis分布式锁_第2张图片

2、分布式锁能解决什么问题?

为了解决多个进程操作共享资源出现竞争,需要保证数据库的安全性

3、分布式锁有哪些常见实现方式(举例说明)

悲观锁、乐观锁:什么是悲观锁和乐观锁 - 知乎

c# redis分布式锁_第3张图片

4、redis分布式锁如何实现?会出现哪些业务问题?

c# redis分布式锁_第4张图片

问题1:死锁(程序抛异常,释放资源没成功,导致于1成功了但是key没释放,其人都还在等待)

c# redis分布式锁_第5张图片

解决方案1:设置超时,确实解决了死锁问题,但是出现新的问题:误解锁

(误解锁:误删的情况,1释放失败后,然后超时自动释放锁,然后2就会创建锁,此时1回来删1自己的锁的时候可能会把2的锁删除,以此类推3456的锁都会新建然后都被其他人误删除掉)

怎么解决误解锁的问题:

那就谁创建谁删除,每个客户端的锁都有自己的唯一标识。

c# redis分布式锁_第6张图片

设计锁重入机制(当客户端创建锁的时候查到了锁,就不用获取锁了,直接跳过锁的机制,直接操作数据库)

但是依然有问题。

只要在多线程操作中出现两行(多行)代码操作redis就会有原子问题

解决方案:

Lua脚本(一个脚本中所有的操作都是原子的)

5、如何使用正确姿势操作redis分布式锁?

可以看视频里的解释,然后还有模仿秒杀的源码:

地址:百度网盘 请输入提取码

提取码:cnmd

分布式锁代码(与源码一致):

#region 秒杀业务测试
        private static readonly string redisConnectionStr = "127.0.0.1:6379,connectTimeout=5000,allowAdmin=false,defaultDatabase=1";
        ///


        /// 秒杀业务
        ///

        private static void TestSeckillDemo()
        {
            //模拟线程数
            var thredNumber = 20;
            //秒杀库存数
            var stockNumber = 3;

            //秒杀成功队列key
            var key = "order_queue";
            //分布式锁key
            var nxKey = "orderNX";

            var csredis = new CSRedisClient(redisConnectionStr);
            csredis.Del(key);
            var isEnd = false;

            // 创建秒杀执行信号量集合
            List taskList = new List();
            // 添加计时器
            Stopwatch stopwatch = new Stopwatch();
            // 开启
            stopwatch.Start();
            for (int i = 0; i < thredNumber; i++)
            {
                int number = i;
                taskList.Add(Task.Run(() =>
                {
                    Thread.Sleep(50);

                    if (isEnd)
                    {
                        Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀失败,抢完了。");
                    }

                    //设置客户端的标识,用于加锁解锁
                    var nxSelfMarkvalue = $"thred{Thread.CurrentThread.ManagedThreadId}_user{number}";
                    //当前线程用户加锁
                    var setnxResult = csredis.RedisLock(nxKey, nxSelfMarkvalue, 1000);
                    if (setnxResult)
                    {
                        var len = csredis.LLen(key);//获取列表长度
                        //成功的队列长度>=库存 (库存不足)
                        if (len >= stockNumber)
                        {
                            isEnd = true;
                            stopwatch.Stop();
                            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀失败,抢完了。");
                            //其实库存不足了,也不用解锁了。如果这里再做解锁操作,其他线程会出现再次加锁,但是返回的还是库存不足。加不加都行
                        }
                        else
                        {
                            var value = $"线程{Thread.CurrentThread.ManagedThreadId}-用户{number}";
                            csredis.LPush(key, value);//名单添加到成功队列
                            //当前线程用户解锁 (nxSelfMarkvalue,防止误解锁)
                            csredis.RedisUnLock(nxKey, nxSelfMarkvalue);
                            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀成功。");
                        }
                    }
                    else
                    {
                        Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 系统繁忙,请稍后再试。 秒杀失败。");
                    }
                }));
            }
            // 等待所有秒杀列表中任务结束
            Task.WaitAll(taskList.ToArray());
            var lenALL = csredis.LLen(key);
            Console.WriteLine($"\r\n秒杀成功人数:{lenALL} 人,用时:{stopwatch.ElapsedMilliseconds} 毫秒.");
            Console.WriteLine($"\r\n是否超售:{(lenALL > stockNumber ? "是" : "否")}");
            Console.WriteLine("\r\n秒杀成功人员名单:");
            for (int i = 0; i < stockNumber; i++)
            {
                Console.WriteLine(csredis.RPop(key));
            }
        }
        #endregion

redis的扩展方法,一个加锁一个解锁的操作

///


    /// Redis分布式锁
    ///

    public static class RedisDistributedLockExtension
    {

        #region 推荐使用
        ///


        /// 加锁毫秒级
        ///

        /// redis客户端连接
        /// 锁key
        /// 锁值
        /// 缓存时间 单位/毫秒 默认1000毫秒
        ///
        public static bool RedisLock(this CSRedisClient client, string key, object value, int expireMilliSeconds = 1000)
        {
            var script = @"local isNX = redis.call('SETNX', KEYS[1], ARGV[1])
                           if isNX == 1 then
                               redis.call('PEXPIRE', KEYS[1], ARGV[2])
                               return 1
                           end
                           return 0";

            return client.Eval(script, key, value, expireMilliSeconds)?.ToString() == "1";
        }

        ///


        /// 解锁
        ///

        /// redis客户端连接
        /// 锁key
        /// 对应加锁客户端标识
        ///
        public static bool RedisUnLock(this CSRedisClient client, string key, string selfMark)
        {
            var script = @"local getLock = redis.call('GET', KEYS[1])
                            if getLock == ARGV[1] then
                              redis.call('DEL', KEYS[1])
                              return 1
                            end
                            return 0";

            return client.Eval(script, key, selfMark)?.ToString() == "1";
        }

        #endregion

    }

你可能感兴趣的:(c#,c#,redis,分布式)