c# redis分布式锁

学习资料:

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

0-2是概念

3-4是代码演示

1、什么是分布式锁

普通锁机制:

悲观锁和乐观锁

分布式锁:

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

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

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

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

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

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

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

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

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

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

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

怎么解决误解锁的问题:

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

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

但是依然有问题。

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

解决方案:

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

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

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

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

提取码:cnmd

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

#region 秒杀业务测试
        private static readonly string redisConnectionStr = "127.0.0.1:6379,connectTimeout=5000,allowAdmin=false,defaultDatabase=1";
        /// <summary>
        /// 秒杀业务
        /// </summary>
        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<Task> taskList = new List<Task>();
            // 添加计时器
            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的扩展方法,一个加锁一个解锁的操作

/// <summary>
    /// Redis分布式锁
    /// </summary>
    public static class RedisDistributedLockExtension
    {

        #region 推荐使用
        /// <summary>
        /// 加锁毫秒级
        /// </summary>
        /// <param name="client">redis客户端连接</param>
        /// <param name="key">锁key</param>
        /// <param name="value">锁值</param>
        /// <param name="expireMilliSeconds">缓存时间 单位/毫秒 默认1000毫秒</param>
        /// <returns></returns>
        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";
        }

        /// <summary>
        /// 解锁
        /// </summary>
        /// <param name="client">redis客户端连接</param>
        /// <param name="key">锁key</param>
        /// <param name="selfMark">对应加锁客户端标识</param>
        /// <returns></returns>
        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

    }

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值