NetCore/Net8下使用Redis的分布式锁实现秒杀功能

news2024/11/16 21:28:21

目的

本文主要是使用NetCore/Net8加上Redis来实现一个简单的秒杀功能,学习Redis的分布式锁功能。

准备工作

1.Visual Studio 2022开发工具

2.Redis集群(6个Redis实例,3主3从)或者单个Redis实例也可以。

实现思路

1.秒杀开始前,将商品的数量缓存到Redis中

2.使用Redis的分布式缓存锁,保证只有一个人能获取到锁,进而保证减库存的操作的原子性。

3.获取到Redis分布式锁后,开始后续的业务操作,减少库存。

实现代码

// See https://aka.ms/new-console-template for more information
using StackExchange.Redis;

WriteLine("开始秒杀活动......");
WriteLine("请输入秒杀商品的ID,按回车键确认:", ConsoleColor.Blue);

//ThreadPool.SetMinThreads(200, 200);

var db = GetDataBase();

string? productId = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(productId))
{
    int maxProductNumber = 100;
    //设置商品的最大库存数量
    await db.StringSetAsync($"ProductNumber:{productId}", maxProductNumber);

    //开始模拟购买
    List<Task> allTaskList = new List<Task>();
    for (int i = 0; i < 1000; i++)
    {
        var task = BuyProduct(db, buyerId: i);
        allTaskList.Add(task);
    }
    await Task.WhenAll(allTaskList);
    int buySuccessNumber = Directory.GetFiles($"{AppContext.BaseDirectory}/buyer/").Length;
    WriteLine($"秒杀产品数量={maxProductNumber},购买成功用户数量={buySuccessNumber}", ConsoleColor.Green);
    Console.ReadLine();
}
else
{
    Console.WriteLine("输入商品ID为空,自动退出");
}

IDatabase GetDataBase()
{
    ConnectionMultiplexer cm = ConnectionMultiplexer.Connect("127.0.0.1:6379,127.0.0.1:6380,127.0.0.1:6381,127.0.0.1:6382,127.0.0.1:6383,127.0.0.1:6384");
    return cm.GetDatabase();
}

async Task BuyProduct(IDatabase db, int buyerId)
{
    int threadId = Environment.CurrentManagedThreadId;
    try
    {
        //首先获取当前库存,判断是否还可以购买
        var leftProductNumber = await GetProductCurrentNumberAsync(db, productId);
        if (leftProductNumber < 1)
        {
            WriteLine($"线程Id={threadId},购买失败,用户Id:{buyerId},库存不足1,当前库存:{leftProductNumber}", ConsoleColor.Red);
            return;
        }
        string key = $"ProductId:{productId}";
        string lockValue = Guid.NewGuid().ToString();
        //锁的过期时间一定要比成功获取锁后操作业务所需的时间长,
        //否则会导致业务还没有操作完成(减库存)锁就释放了,导致后面的用户获取到锁,最终导致超卖的情况
        bool lockSuccess = await GetLockAsync(db, key, lockValue, TimeSpan.FromSeconds(5));
        if (!lockSuccess)
        {
            WriteLine($"线程Id={threadId},用户Id={buyerId},购买锁获取失败", ConsoleColor.Red);
            return;
        }

        try
        {
            //再次获取当前库存,判断是否还可以购买
            leftProductNumber = await GetProductCurrentNumberAsync(db, productId);
            if (leftProductNumber < 1)
            {
                WriteLine($"线程Id={threadId},购买失败:{lockValue},用户Id:{buyerId},库存不足2,当前库存:{leftProductNumber}", ConsoleColor.Red);
                return;
            }
            //扣减库存
            await db.StringDecrementAsync($"ProductNumber:{productId}");

            WriteLine($"线程Id={threadId},购买成功:{lockValue},用户Id:{buyerId}", ConsoleColor.Green);
            var dirPath = $"{AppContext.BaseDirectory}/buyer";
            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
            }
            await File.WriteAllTextAsync($"{dirPath}/buy-success-{buyerId}.txt", $"锁Id={lockValue},用户Id={buyerId},产品Id={productId},剩余产品数量={leftProductNumber}");
        }
        finally
        {
            bool lockReleased = await db.LockReleaseAsync(key, lockValue);
            if (!lockReleased)
            {
                WriteLine($"线程Id={threadId},用户Id={buyerId},锁释放失败:{lockValue}", ConsoleColor.Yellow);
            }
        }
    }
    catch(Exception ex)
    {
        WriteLine($"线程Id={threadId},用户Id={buyerId},购买失败:{ex}", ConsoleColor.Red);
    }
}

async Task<bool> GetLockAsync(IDatabase db, string key, string lockValue, TimeSpan timeout)
{
    //每个用户有五次获取Redis分布式产品锁的机会,如果5次重试后,都没有获取到锁,则默认秒杀失败
    int i = 5;
    while (i > 0)
    {
        bool lockSuccess = await db.LockTakeAsync(key, lockValue, timeout);
        if (lockSuccess)
        {
            return true;
        }
        await Task.Delay(TimeSpan.FromMilliseconds(new Random(Guid.NewGuid().GetHashCode()).Next(100, 500)));
        i--;
    }
    return false;
}

async Task<long> GetProductCurrentNumberAsync(IDatabase db, string productId)
{
    string? leftProductNumberString = await db.StringGetAsync($"ProductNumber:{productId}");
    _ = long.TryParse(leftProductNumberString, out long leftProductNumber);
    return leftProductNumber;
}

static void WriteLine(string text, ConsoleColor colour = ConsoleColor.White)
{
    Console.ForegroundColor = colour;
    Console.WriteLine(text);
}

运行效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1106623.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SVM+Webdriver的智能NBA常规赛与季后赛结果预测系统——机器学习算法应用(含python、ipynb工程源码)+所有数据集(一)

目录 前言总体设计系统整体结构图系统流程图 运行环境Python环境Jupyter Notebook环境PyCharm环境MATLAB环境 模块实现1. 数据预处理1&#xff09;常规赛数据处理2&#xff09;季后赛数据处理 相关其它博客工程源代码下载其它资料下载 前言 本项目使用了从NBA官方网站获得的数…

基于SVM+Webdriver的智能NBA常规赛与季后赛结果预测系统——机器学习算法应用(含python、ipynb工程源码)+所有数据集(三)

目录 前言总体设计系统整体结构图系统流程图 运行环境模块实现1. 数据预处理2. 特征提取3. 模型训练及评估1&#xff09;常规赛预测模型2&#xff09;季后赛模型创建 4. 模型训练准确率 相关其它博客工程源代码下载其它资料下载 前言 本项目使用了从NBA官方网站获得的数据&…

如何使用 Disco 将黑白照片彩色化

Disco 是一个基于视觉语言模型&#xff08;LLM&#xff09;的图像彩色化工具。它使用 LLM 来生成彩色图像&#xff0c;这些图像与原始黑白图像相似。 本文将介绍如何使用 Disco 将黑白照片彩色化。 使用 Disco 提供了一个简单的在线演示&#xff0c;可以用于测试模型。 访问…

关于gt_sampling的理解

pcdet/datasets/augmentor/data_augmentor.py def gt_sampling(self, configNone):db_sampler database_sampler.DataBaseSampler(root_pathself.root_path,sampler_cfgconfig,class_namesself.class_names,loggerself.logger)return db_sampler此函数指向DataBaseSampler类&a…

【vSphere 8 自签名证书】企业 CA 签名证书替换 vSphere Machine SSL 证书Ⅱ—— 创建和添加证书模板

目录 博文摘要3. 使用 Microsoft 证书颁发机构创建 Machine SSL 和 Solution User 证书模板3.1 打开 Certificate Template Console3.2 复制模板3.3 修改 Compatibility 选项卡3.4 修改 General 选项卡3.5 修改 Extensions 选项卡3.6 修改 Subject Name 选项卡3.7 确认新模板 4…

【送书福利-第十九期】《C++ Core Guidelines解析》

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主、前后端开发、人工智能研究生。公粽号&#xff1a;程序员洲洲。 &#x1f388; 本文专栏&#xff1a;本文…

Ant Eclipse插件使用

Eclipse默认带了ant插件 编辑build.xml文件给出提示 编辑的时候&#xff0c;会给出提示&#xff0c;方便编辑&#xff1a; 将鼠标放在属性上方&#xff0c;会将属性的值显示出来&#xff1a; 在Eclipse中运行ant 运行默认的target build.xml文件的内容如下&#xff0c;…

编程入门到精通:开源学习资料整理 | 开源专题 No.37

ascoders/weekly Stars: 24.8k License: NOASSERTION 前端精读是一个每周更新的前端好文精选项目。该项目涵盖了多个领域&#xff0c;包括结合大厂工作经验解读的前沿技术、源码解读、一些后端技术解读和商业思考等内容。主要功能是为开发者提供优质的文章资源&#xff0c;帮…

springBoot web开发自动配置和默认效果

web开发自动配置和默认效果 自动配置默认配置 自动配置 绑定了配置文件的一堆配置项 1、springMVC的所有配置 spring.mvc 2、Web场景通用配置 spring.web 3、文件上传配置 spring.servlet.multipart 4、服务器的配置serve: 比如&#xff1a;编码方式等 默认配置 重要&#xf…

【异常、线程】全网最详细解读

【异常、线程】 主要内容 异常、线程 教学目标 能够辨别程序中异常和错误的区别 说出异常的分类 说出虚拟机处理异常的方式 列举出常见的三个运行期异常 能够使用try…catch关键字处理异常 能够使用throws关键字处理异常 能够自定义异常类 能够处理自定义异常类 说出进程的概…

python案例:六大主流小说平台小说下载

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 很多小伙伴学习Python的初衷就是为了爬取小说&#xff0c;方便又快捷~ 辣么今天咱们来分享6个主流小说平台的爬取教程~ 一、流程步骤 流程基本都差不多&#x…

Pi-hole:Linux 硬件级别的广告拦截器 | 开源日报 No.58

pi-hole/pi-hole Stars: 44.0k License: NOASSERTION Pi-hole 是一个通过自己的 Linux 硬件实现网络广告拦截的 DNS 陷阱&#xff0c;无需安装任何客户端软件即可保护设备免受不需要的内容干扰。 安装简单&#xff1a;对话框引导您在十分钟内完成简单安装过程坚决有效&#…

Linux进程上下文切换:理解特权模式和进程切换

Linux进程上下文切换&#xff1a;理解特权模式和进程切换 在Linux中&#xff0c;进程的运行空间被划分为内核空间和用户空间&#xff0c;而从用户态向内核态转换需要进行系统调用。这一过程中发生了两次CPU上下文切换&#xff1a; 00001. 保存用户态&#xff1a;将CPU寄存器…

数组之移除元素

本文旨在复习巩固 此题为leetcode上的27题 数组的元素在内存地址中是连续的&#xff0c;不能单独删除数组中的某个元素&#xff0c;只能覆盖。 1 暴力解法 如图所示&#xff0c;该方法是十分麻烦的&#xff0c;因为每次找到val&#xff0c;都要删除它&#xff0c;让后面的元…

软件开发“自我毁灭”的七宗罪

软件开发是一门具有挑战性的学科&#xff0c;它建立在数以百万计的参数、变量、库以及更多必须绝对正确的因素之上。即便是一个字符不合适&#xff0c;整个堆栈也会随之瓦解。 多年来&#xff0c;软件开发团队已经想出了一些完成工作的规则。从复杂的方法论到新兴的学科和哲学…

Linux文件系统 struct inode 结构体解析

文章目录 前言一、inode 简介二、dentry 简介三、struct inode3.1 字段说明3.2 inode链表3.3 struct inode_operations3.4 inode相关函数 参考资料 前言 这篇文章介绍了VFS - struct file&#xff1a;Linux文件系统 struct file 结构体解析 接下来介绍VFS - struct inode&…

回首往昔,初学编程那会写过的两段愚蠢代码

一、关于判断两个整数是否能整除的GW BASIC创意代码 记得上大学时第一个编程语言是BASIC&#xff0c;当时Visual Basic还没出世&#xff0c;QBASIC虽然已经在1991年随MS-DOS5.0推出了&#xff0c;但我们使用的还是 GW-BASIC&#xff0c; 使用的教材是谭浩强、田淑清编著的《BA…

Linux虚拟机静态IP设置

1.环境配置 首先要准备好两台centos虚拟机&#xff0c;在高级篇部分可能有数据库主从复制&#xff0c;所以暂时先开两台虚拟机。 两台虚拟机需要更改&#xff1a; mac地址主机名ip地址UUID 1.更改主机名 更改虚拟机的主机名 vim /etc/hostname2.更改静态ip地址 /etc/sysc…

basic_sr介绍

文章目录 pytorch基础知识和basicSR中用到的语法1.Sampler类与4种采样方式2.python dict的get方法使用3.prefetch_dataloader.py4. pytorch 并行和分布式训练4.1 选择要使用的cuda4.2 DataParallel使用方法常规使用方法保存和载入 4.3 DistributedDataParallel 5.wangdb 入门5.…

5秒用Java写一个快速排序算法?这个我在行

快速排序是一种非常高效的排序算法&#xff0c;由英国计算机科学家霍尔在1960年提出。它的基本思想是选择一个基准元素将待排序数组分成两部分&#xff0c;其中一部分的所有元素都比基准元素小&#xff0c;另一部分的所有元素都比基准元素大&#xff0c;然后对这两部分再分别进…