• 【愚公系列】2022年11月 .NET CORE工具案例-CSRedis执行Lua脚本实现商品秒杀



    前言

    下面是Redis分布式锁常用的概念说明:设置、获取、过期时间、删除。

    1、 Setnx

    • 命令:SETNX key value
    • 说明:将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作。SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。 时间复杂度:O(1)
    • 返回值:设置成功,返回1 ; 设置失败,返回 0

    2、Getset

    • 命令:GETSET key value
    • 说明:将给定 key 的值设为 value ,并返回 key 的旧值(old value)。当 key 存在但不是字符串类型时,返回一个错误。 时间复杂度:O(1)
    • 返回值:返回给定 key 的旧值; 当 key 没有旧值时,也即是, key 不存在时,返回 nil 。

    3、Expire

    • 命令:EXPIRE key seconds
    • 说明:为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。 时间复杂度:O(1)
    • 返回值:设置成功返回 1 ;当 key 不存在或者不能为 key 设置生存时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的生存时间),返回 0 。

    4、Del

    • 命令:DEL key [key …]
    • 说明:删除给定的一个或多个 key 。不存在的 key 会被忽略。 时间复杂度:O(N); N 为被删除的 key 的数量。删除单个字符串类型的 key ,时间复杂度为O(1)。 删除单个列表、集合、有序集合或哈希表类型的 key ,时间复杂度为O(M), M为以上数据结构内的元素数量。

    锁的分类说明:

    相对方相对方
    悲观锁乐观锁
    公平锁非公平锁
    独享锁共享锁
    线程锁进程锁

    一、CSRedis执行Lua脚本实现商品秒杀

    以下以.NET 7控制台为实例测试

    1.单线程模拟多线程进行秒杀

    using CSRedis;
    
    Console.WriteLine("Hello World!");
    //连接redis客户端
    CSRedisClient redisClient = new CSRedisClient("127.0.0.1:6379,defaultDatabase=1");
    //锁键
    var lockKey = "lockKey";
    //库存数
    var stockKey = "stock";
    //设置库存数量为5
    redisClient.Set(stockKey, 5);//商品库存
    //lua脚本,也可以通过流形式加载进来
    var releaseLockScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";//释放锁的redis脚本
    redisClient.Del(lockKey);//测试前,先把锁删了.
    
    //------------------Parallel可以控制线程的执行顺序-----------------------------------
    //ParallelOptions parallelOptions = new ParallelOptions();
    //parallelOptions.MaxDegreeOfParallelism = 2;//最大并行数
    //Parallel.For(0, 10, parallelOptions, t => this.DoSomethingLong($"btnParallel_Click_{t}"));
    Parallel.For(0, 10, i =>
    {
        var id = Guid.NewGuid().ToString("N");
        //获取锁,有效期5秒,防止死锁产生
        do
        {
            //set : key存在则失败,不存在才会成功,并且过期时间5秒
            var success = redisClient.Set(lockKey, id, expireSeconds: 5, exists: RedisExistence.Nx);
            if (success == true)
            {
                break;
            }
            Thread.Sleep(TimeSpan.FromSeconds(1));//休息1秒再尝试获取锁
        } while (true);
        Console.WriteLine($"线程:{Task.CurrentId} 拿到了锁,开始消费");
        //扣减库存
        var currentStock = redisClient.IncrBy(stockKey, -1);
        if (currentStock < 0)
        {
            Console.WriteLine($"库存不足,线程:{Task.CurrentId} 抢购失败!");
            redisClient.Eval(releaseLockScript, lockKey, id);
            return;
        }
        //模拟处理业务,这里不考虑失败的情况
        Thread.Sleep(TimeSpan.FromSeconds(new Random().Next(1, 3)));
        Console.WriteLine($"线程:{Task.CurrentId} 消费完毕!剩余 {currentStock} 个");
        //业务处理完后,释放锁.
        redisClient.Eval(releaseLockScript, lockKey, id);
    });
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    在这里插入图片描述
    在这里插入图片描述

    2.多线程进行秒杀

    运行两个进程测试秒杀情况

    using CSRedis;
    
    Thread.Sleep(500);
    
    Console.WriteLine("Hello World!");
    CSRedisClient redisClient = new CSRedisClient("127.0.0.1:6379,defaultDatabase=1");
    var lockKey = "lockKey";
    //库存数
    var stockKey = "stock";
    //设置库存数量为5
    redisClient.Set(stockKey, 10000, exists: RedisExistence.Nx);//商品库存
    
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(500);
    
        var releaseLockScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";//释放锁的redis脚本
        var id = Guid.NewGuid().ToString("N");
        //set : key存在则失败,不存在才会成功,并且不过期
        var success = redisClient.Set(lockKey, id, expireSeconds: -1, exists: RedisExistence.Nx);
        if (success != true)
        {
            Console.WriteLine("当前资源以被占用,没机会了,等10秒钟再次尝试");
            Thread.Sleep(1000);//休息1秒再尝试获取锁
            continue;
        }
        else
        {
            Console.WriteLine($"线程:{Task.CurrentId} 拿到了锁,开始消费");
        }
    
        ///业务操作
        var currentStock = redisClient.IncrBy(stockKey, -1);
        if (currentStock < 0)
        {
            Console.WriteLine($"库存不足,线程:{Task.CurrentId} 抢购失败!");
            redisClient.Eval(releaseLockScript, lockKey, id);
            return;
        }
        //模拟处理业务,这里不考虑失败的情况
        Thread.Sleep(TimeSpan.FromSeconds(new Random().Next(1, 3)));
        Console.WriteLine($"线程:{Task.CurrentId} 消费完毕!剩余 {currentStock} 个");
        //业务处理完后,释放锁.
        redisClient.Eval(releaseLockScript, lockKey, id);
    
    }
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    在这里插入图片描述

  • 相关阅读:
    伙伴计划|“团团活力圈”--巧编中国结 传承中国梦
    hcia 目的mac为(单播 组播 广播)mac
    摄影工作室标配,智云五倍F100棒灯快速塑造专业风格
    【云原生之kubernetes实战】在k8s环境下部署OrangeHRM人力资源管理系统
    mysql源码分析——InnoDB的磁盘结构
    Java实现数据结构——双链表
    Q学习与Sarsa
    【卫朋】结构设计:如何使用 PROE 导出 CAD 文件?
    docker实战学习2022版本(七)之docker网络学习
    科大讯飞交卷,实测星火大模型
  • 原文地址:https://blog.csdn.net/aa2528877987/article/details/127820363