.net实现秒杀商品(Redis高并发)

1.思想

模拟不同用户在同一时刻对一件商品进行秒杀,每次调用随机一个userId,代表不同的用户。

设置三个键

userRecordKey(记录用户是否已经秒杀过这个商品),

stockKey(商品的库存),

lockKey(互斥锁,是分布式锁机制的核心标识。过这个键的 "存在 / 不存在" 来标识 "是否有线程在操作华为手机的秒杀资源",从而实现 多线程或者多服务器之间的互斥访问。当某个线程获取锁时,会在 Redis 中创建这个 lockKey。其他线程尝试获取锁时,会检查这个lockKey是否存在,存在则表示 "资源被占用",需等待或放弃;线程释放锁时,会删除这个 lockKey,释放资源占用。)

如果有一个进程在进行操作,其他线程就会显示秒杀拥挤,等待当前进程释放锁。具体业务逻辑,判断是否有锁后,如果没有则判断是否重复下单 -》检查库存 -》 扣减库存 -》 记录用户 -》释放锁。

2.业务代码

复制代码
 #region stackRedis封装
 ///<summary>
 /// stackredis
 /// </summary>
 [HttpGet("stackRedis")]
 [AllowAnonymous]
 public IActionResult StackRedis()
 {
     int userId = _threadLocalRandom.Value.Next(1000, 10_000);
     var userRecordKey = $"usersession:seckill:user:record:{userId}";
     const string stockKey = "usersession:seckill:stock:huawei_phone";
     const string lockKey = "usersession:seckill:lock:huawei_phone";
     var lockVal = Guid.NewGuid().ToString();
     // 1.原子拿锁
     bool getKey = StackExchangeRedisHelper.Db.StringSet(lockKey, lockVal,TimeSpan.FromSeconds(10),When.NotExists);
     Thread.Sleep(10000);
     if (!getKey)
         return ToResponse(ResultCode.CUSTOM_ERROR, "秒杀拥挤!");
     try
     {
         // 2.判断重复下单
         bool reBuy = StackExchangeRedisHelper.Db.KeyExists(userRecordKey);
         if (reBuy)
             return ToResponse(ResultCode.CUSTOM_ERROR,"重复下单!");
         // 3.检查库存
         int stock = (int)StackExchangeRedisHelper.Db.StringGet(stockKey);
         if (stock <= 0)
             return ToResponse(ResultCode.CUSTOM_ERROR, "商品已抢完");
         // 4.减库存
         int a = (int)StackExchangeRedisHelper.Db.StringDecrement(stockKey);
         Console.WriteLine($"当前有{a}");
         // 5.记录用户
         StackExchangeRedisHelper.Db.StringSet(userRecordKey,1,TimeSpan.FromMinutes(10));
         return SUCCESS("秒杀成功!");
     }
     finally
     {
         //释放锁
         try
         {
             var lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
             StackExchangeRedisHelper.Db.ScriptEvaluate(lua, new RedisKey[] { lockKey }, new RedisValue[] { lockVal });
         }
         catch (Exception ex)
         {
             
             Console.WriteLine(ex+"释放 Redis 锁失败,lockKey: {lockKey}"+lockKey);
         }
     }
 }
 #endregion

3. StackExchangeRedisHelper.cs

基于 StackExchange.Redis 的单例 Redis 连接助手,考虑了连接的自动重建和旧连接的延迟释放,,也可以在controller里自己连接,我觉得这样方便,美观点。

复制代码
using Infrastructure;
using StackExchange.Redis;
using System;
using System.Threading.Tasks;
namespace ZR.Infrastructure.Helper
{
    /// <summary>
    /// Redis,连接单例
    /// </summary>
    public static class StackExchangeRedisHelper
    {
        private static readonly object _lock = new object();
        private static ConnectionMultiplexer _mux;
        ///<summary>
        /// 连接字符串,可在 Program.cs 或 appsettings.json 里先赋值
        /// </summary>
        
        private static string config = AppSettings.GetConfig("StackRedis:Redis") + ",connectTimeout=5000,syncTimeout=3000";
        // <summary>
        /// 单例 ConnectionMultiplexer
        /// </summary>
        public static ConnectionMultiplexer Mux
        {
            get
            {
                if (_mux == null || !_mux.IsConnected)
                {
                    lock (_lock)
                    {
                        if (_mux == null || !_mux.IsConnected)
                        {
                            // 关键修改:不直接 Dispose 旧连接,而是创建新连接后让旧连接被 GC 回收
                            // (避免正在使用旧连接的请求报错)
                            var oldMux = _mux;
                            _mux = ConnectionMultiplexer.Connect(config); // 创建新连接

                            // 延迟释放旧连接(给正在使用的请求留时间完成)
                            if (oldMux != null)
                            {
                                Task.Run(() =>
                                {
                                    try
                                    {
                                        // 等待 5 秒后释放旧连接(根据业务调整)
                                        Task.Delay(5000).Wait();
                                        oldMux.Dispose();
                                    }
                                    catch { /* 忽略释放失败的异常 */ }
                                });
                            }
                        }
                    }
                }
                return _mux;
            }
        }
        /// <summary>
        /// 默认数据库(0 号)
        /// </summary>
        public static IDatabase Db => Mux.GetDatabase();
        /// <summary>
        /// 按需获取指定数据库
        /// </summary>
        public static IDatabase GetDatabase(int db = 0) => Mux.GetDatabase(db);
    }
}

4.Appsettings.json

复制代码
"StackRedis": {
  "Redis": "localhost:6379"
}

5.jemeter测试(200个线程)

6.结果

7.问题

感觉还是不完美,没有使用异步,没有最大化并发处理能力,也没有事务回滚,在后续我将继续完善

相关推荐
缺点内向1 小时前
如何在 C# .NET 中将 Markdown 转换为 PDF 和 Excel:完整指南
pdf·c#·.net·excel
啦啦啦~~~7541 小时前
【最新版】Edge浏览器安装!绿色增强版+禁止Edge更新的软件+彻底卸载Edge软件
数据库·阿里云·电脑·.net·edge浏览器
小兜全糖(xdqt)2 小时前
.net 8 添加swagger以及批量index,批量删除 elasticsearch
elasticsearch·jenkins·.net
向宇it2 小时前
【unity游戏开发——网络】unity对接steam,并上传发布游戏版本——Steamworks.NET
游戏·unity·游戏引擎·.net·交互
武藤一雄2 小时前
彻底吃透.NET中序列化反序列化
xml·微软·c#·json·.net·.netcore
切糕师学AI3 小时前
.NET 中常见的内存泄漏场景及解决方案
.net·内存泄漏
专注VB编程开发20年13 小时前
C#全面超越JAVA,主要还是跨平台用的人少
java·c#·.net·跨平台
一个帅气昵称啊1 天前
.Net通过EFCore和仓储模式实现统一数据权限管控并且相关权限配置动态生成
.net·efcore·仓储模式
helloworddm1 天前
CalculateGrainDirectoryPartition
服务器·c#·.net
步步为营DotNet1 天前
深度剖析.NET中HttpClient的请求重试机制:可靠性提升与实践优化
开发语言·php·.net