dotnet基础开发之-Redis分布式锁

1、分布式锁的应用场景

目的:在分布式部署的环境下,协调多个进程/服务对共享资源的互斥访问,保证数据一致性。

场景:

1)防止重复提交: 用户快速多次点击下单、付款、表单提交等,避免生成重复订单或数据。

2)分布式全局ID生成:在分库分表场景下需要生成全局唯一且递增的ID(如订单号,流水号),通过分布式锁获取一段

3)避免缓存击穿,缓存重建:热点Key过期后,大量请求同步打到数据库,导致DB压力骤增,通过使用互斥锁,第一个请求获取锁后去数据库查询并重建缓存,其他请求等待或返回旧数据。

4)服务注册中心的健康检查防重:多实例同时检测到服务下线,只需要一个实例去通知注册中心更新状态。使用分布式锁确保只有一个执行者。

......

2、分布式锁的实现技术

2.1、CAP的三个概念

C(一致性):所有节点同一时刻看到的数据是相同的。对于客户端,写操作完成后,读操作必须返还最新的数据。

A(可用性):服务始终能返回响应,每个请求都能收到非错误的响应

P(分区容错性):当网络出现分区(节点之间无法通信)时,系统仍能继续工作。

实现方案 优点 缺点 CAP 倾向 适用场景
Redis 性能极高,实现简单,易上手 强一致性难保证,锁可能在主从切换时丢失 AP 高性能场景,可容忍极低概率的锁失效
ZooKeeper 强一致性,有可靠的通知机制,无单点问题 性能相对较差,依赖复杂,有"惊群"效应- CP 对数据一致性要求高的金融、交易场景
etcd 强一致性,内置租约机制,设计现代,API友好 生态成熟度较前两者稍弱 CP 云原生环境,追求高可靠性的系统
数据库 简单直观,无额外组件成本 性能差,有死锁风险,可能拖垮数据库 CA 并发极低的简单场景,或作为极端兜底方案

3、使用Redis实现分布式锁

3.1、创建一个WebApi项目 Demos.MicrosoftRedLockNet 1)引入nuget包

csproj 复制代码
<ItemGroup>

  <!-- 官方推荐的 .NET 分布式锁实现 -->

  <PackageReference Include="RedLock.net" Version="2.3.2" />

  <PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.5" />

  <PackageReference Include="MySqlConnector" Version="2.3.7" />

  <PackageReference Include="Mapster" Version="7.4.0" />

  <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.5" />

  <PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />

  <!--数据迁移-->

  <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.5">

    <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>

    <PrivateAssets>compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native</PrivateAssets>

  </PackageReference>

  <!--AOP方式实现-->

  <PackageReference Include="Autofac" Version="7.1.0" />

  <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" />

  <PackageReference Include="Autofac.Extras.DynamicProxy" Version="7.1.0" />

  <PackageReference Include="Castle.Core" Version="5.1.1" />

  <PackageReference Include="StackExchange.Redis" Version="2.7.33" />

</ItemGroup>
  

2)创建2个实体,这里只是模拟锁的使用,不限定业务环境

csharp 复制代码
1、商品库存表
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Demos.MicrosoftRedLockNet.Entities
{
    /// <summary>
    /// 商品库存表
    /// </summary>
    [Table("shop_inventory")]
    [Comment("商品库存表")]
    public class ShopInventory
    {
        /// <summary>
        /// 库存表Id
        /// </summary>
        [Key]
        public int Id { get; set; }
        /// <summary>
        /// 商品Id
        /// </summary>
        public int ProductId { get; set; }
        /// <summary>
        /// 库存数
        /// </summary>
        public int Total { get; set;  }
    }
}
2、订单表
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Demos.MicrosoftRedLockNet.Entities
{
    /// <summary>
    /// 订单信息表
    /// </summary>
    [Table("order_info")]
    [Comment("订单信息表")]
    public class ShopOrder
    {
        /// <summary>
        /// 订单表Id
        /// </summary>
        [Key]
        public Guid Id { get; set; }  
        /// <summary>
        /// 用户ID
        /// </summary>
        public int UserId { get; set;  }
        /// <summary>
        /// 商品ID
        /// </summary>
        public int ProductId { get; set;  }
        /// <summary>
        /// 订单号
        /// </summary>
        [Display(Name = "序号")]
        [Required(AllowEmptyStrings = false)]
        [Column(TypeName = "varchar(200)")]
        public string OrderNum { get; set; }
        /// <summary>
        /// 商品数量
        /// </summary>
        public int OrderQuantity { get; set; }
        /// <summary>
        /// 订单金额
        /// </summary>
        [Column(TypeName = "decimal(8, 2)")]
        public decimal Price { get; set; }
        /// <summary>
        /// 支付时间
        /// </summary>
        public DateTime? PayTime { get; set; }
        /// <summary>
        /// 支付订单号
        /// </summary>
        public string PayOrderNum { get; set; }
        /// <summary>
        /// CreateTime
        /// </summary>
        public DateTime? CreateTime { get; set; }
        /// <summary>
        /// 订单备注
        /// </summary>
        [Column(TypeName = "varchar(200)")]
        public string Remark { get; set; }
    }
}

3)创建DbContext

csharp 复制代码
using Demos.MicrosoftRedLockNet.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using System.Collections;
using System.Data;

namespace Demos.MicrosoftRedLockNet.Contexts
{
    public class MyDbContext : DbContext
    {
        public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
        {
        }
        public DbSet<ShopOrder> ShopOrder { get; set; }
        public DbSet<ShopInventory> ShopInventory { set; get; }
    }
}

4)配置appsettings.json

json 复制代码
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.EntityFrameworkCore.Database.Command": "Warning"
    }
  },
  "ConnectionStrings": {
    "Redis": "localhost:6379",
    "Default": "Server=localhost;Port=3306;Database=demo_orderservice;Uid=root;Pwd=123456;"
  },
  "AllowedHosts": "*"
}

3.2、创建数据库

1)先创建demo_orderservice数据库,采用utf8mb4字符集,utf8mb4_general_ci排序规则

2)执行数据迁移

shell 复制代码
//生成项目
PS D:\Workspace\DotNet\netCoreDemos\dev\Demos.MicrosoftRedLockNet> dotnet build

//生成迁移文件
PS D:\Workspace\DotNet\netCoreDemos\dev\Demos.MicrosoftRedLockNet> dotnet ef migrations add MicrosoftRedLockNetMigrations

//执行迁移生成文件,执行前检查项目中是否生成了MIgrations文件夹以及相关迁移文件
PS D:\Workspace\DotNet\netCoreDemos\dev\Demos.MicrosoftRedLockNet> dotnet ef database update

3.3、创建service层

1)创建IOrderService.cs接口

csharp 复制代码
using Demos.MicrosoftRedLockNet.AopLocks;
using Demos.MicrosoftRedLockNet.Common;
using Demos.MicrosoftRedLockNet.Services.Dtos;
namespace Demos.MicrosoftRedLockNet.Services
{
    /// <summary>
    /// 订单服务
    /// </summary>
    public interface IOrderService
    {
        /// <summary>
        /// 创建订单
        /// </summary>
        /// <param name="orderDto"></param>
        /// <returns></returns>
        [MyRedisLock(Key = "seckill:product:{orderDto.ProductId}", WaitTime = 3, LeaseTime = 10, FailMessage = "系统繁忙,请稍后重试")]
        Task<ResultObject> CreateOrderAsync(ShopOrderDto orderDto);
    }
}

2)创建实现类OrderServiceAopInstance.cs

csharp 复制代码
using Demos.MicrosoftRedLockNet.Common;
using Demos.MicrosoftRedLockNet.Contexts;
using Demos.MicrosoftRedLockNet.Entities;
using Demos.MicrosoftRedLockNet.Services.Dtos;
using Mapster;
using Microsoft.EntityFrameworkCore;
using StackExchange.Redis;

namespace Demos.MicrosoftRedLockNet.Services
{

    public class OrderServiceAopInstance : IOrderService
    {
        private readonly MyDbContext _dbContext;

        private readonly IDatabase _redisDb;

        public OrderServiceAopInstance(MyDbContext dbContext, IConnectionMultiplexer redisConnection)
        {
            _dbContext = dbContext;
            _redisDb = redisConnection.GetDatabase(1);
        }

        /// <summary>
        /// 商品抢购
        /// </summary>
        /// <param name="orderDto"></param>
        /// <returns></returns>
        public async Task<ResultObject> CreateOrderAsync(ShopOrderDto orderDto)
        {
            var result = new ResultObject();
            if (orderDto == null || orderDto.ProductId <= 0 || orderDto.ProductCount <= 0)
            {
                result.code = 1004;
                result.message = "抢购失败,参数错误";
                return result;
            }

            // 1. 预热/获取库存
            string stockKey = $"inventory:product:{orderDto.ProductId}";
            long redisStock = await GetOrWarmupStockAsync(orderDto.ProductId, stockKey);
            if (redisStock < orderDto.ProductCount)
            {
                result.code = 1001;
                result.message = "库存不足";
                return result;
            }

            // 2. 原子扣减 Redis 库存
            long newStock = await _redisDb.StringDecrementAsync(stockKey, orderDto.ProductCount);
            if (newStock < 0)
            {
                await _redisDb.StringIncrementAsync(stockKey, orderDto.ProductCount);
                result.code = 1001;
                result.message = "库存不足";
                return result;
            }
            // 3. 数据库事务(扣库存 + 创建订单)
            await using var transaction = await _dbContext.Database.BeginTransactionAsync();
            try
            {
                bool deductOk = await DeductInventoryFromDbAsync(orderDto.ProductId, orderDto.ProductCount);
                if (!deductOk)
                {
                    result.code = 1002;
                    result.message = "库存扣减失败";
                    return result;
                }

                bool orderOk = await InsertOrderRecordAsync(orderDto);
                if (!orderOk)
                {
                    result.code = 1003;
                    result.message = "创建订单失败";
                    return result;
                }
                await transaction.CommitAsync();
            }
            catch
            {
                await transaction.RollbackAsync();
                await _redisDb.StringIncrementAsync(stockKey, orderDto.ProductCount);
                result.code = 1004;
                result.message = "抢购失败";
                return result;
            }
            result.message = $"抢购成功,当前库存={newStock}";
            result.code = 1000;
            return result;
        }

        /// <summary>
        /// 添加订单记录
        /// </summary>
        /// <returns></returns>
        private async Task<bool> InsertOrderRecordAsync(ShopOrderDto orderInfo)
        {
            if (orderInfo == null) return false;
            var order = orderInfo.Adapt<ShopOrder>();
            order.Id = Guid.NewGuid();
            order.OrderNum = Guid.NewGuid().ToString("N");
            order.CreateTime = DateTime.Now;
            order.PayOrderNum = Guid.NewGuid().ToString("N");
            order.OrderQuantity = orderInfo.ProductCount;
            order.Remark = "秒杀活动下单";
            order.Price = 19.99M;
            await _dbContext.ShopOrder.AddAsync(order);
            var result = await _dbContext.SaveChangesAsync();
            return result > 0;
        }

        /// <summary>
        /// 扣减库存
        /// </summary>
        /// <param name="productId"></param>
        /// <param name="quantity"></param>
        /// <returns></returns>
        private async Task<bool> DeductInventoryFromDbAsync(long productId, int quantity)
        {
            //使用原子更新,防止库存跳动
            int affectedRows = await _dbContext.ShopInventory
              .Where(i => i.ProductId == productId && i.Total >= quantity)
              .ExecuteUpdateAsync(s => s.SetProperty(i => i.Total, i => i.Total - quantity));
            return affectedRows > 0;
        }
        /// <summary>
        /// 查询数据库库存
        /// </summary>
        /// <param name="productId"></param>
        /// <returns></returns>
        private async Task<int> GetOrWarmupStockAsync(long productId, string stockKey)
        {
            // 1. 先检查 Redis 中是否已有库存
            if (await _redisDb.KeyExistsAsync(stockKey))
            {
                var stock = await _redisDb.StringGetAsync(stockKey);
                return (int)stock;
            }
            // 2. 只有 Redis 没有时,才去查数据库并预热
            try
            {
                var inventory = await _dbContext.ShopInventory
                    .Where(i => i.ProductId == productId)
                    .FirstOrDefaultAsync();
                if (inventory?.Total > 0)
                {
                    // 加上过期时间,防止死数据
                    await _redisDb.StringSetAsync(stockKey, inventory.Total, TimeSpan.FromHours(1));
                }
                return inventory?.Total ?? 0;
            }
            catch (Exception)
            {
                return 0;
            }
        }
    }
}
返回类型  
namespace Demos.MicrosoftRedLockNet.Common
{
    public class ResultObject
    {
        public bool status { get; set; } = true;
        public int code { get; set; }
        public string message { get; set; }
        public static ResultObject Fail(string msg)
        {
            return new ResultObject
            {
                status = false,
                code = 0,
                message = msg
            };
        }
        public static ResultObject Success(string msg)
        {
            return new ResultObject
            {
                code = 200,
                message = msg
            };
        }
    }
}

3.4、创建AOP方法

csharp 复制代码
//1、参数读取类LockKeyParser

using System.Reflection;
using System.Text.RegularExpressions;
namespace Demos.MicrosoftRedLockNet.AopLocks
{
    /// <summary>
    /// 请求参数读取
    /// </summary>
    public class LockKeyParser
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="arguments"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static string Parse(string expression, object[] arguments, ParameterInfo[] parameters)
        {
            if (string.IsNullOrEmpty(expression)) return expression;
            // 匹配 {参数名} 或 {参数名.属性名} 格式
            var regex = new Regex(@"\{([^{}]+)\}");
            return regex.Replace(expression, match =>
            {
                string path = match.Groups[1].Value;
                var value = GetValueFromArguments(path, arguments, parameters);
                return value?.ToString() ?? "";
            });
        }


        /// <summary>
        /// 从action读取参数
        /// </summary>
        /// <param name="path"></param>
        /// <param name="arguments"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        private static object GetValueFromArguments(string path, object[] arguments, ParameterInfo[] parameters)
        {
            var parts = path.Split('.');
            var firstPart = parts[0];


            // 查找参数
            var paramIndex = Array.FindIndex(parameters, p => p.Name == firstPart);
            if (paramIndex == -1) return null;


            var current = arguments[paramIndex];
            for (int i = 1; i < parts.Length && current != null; i++)
            {
                current = current.GetType().GetProperty(parts[i])?.GetValue(current);
            }
            return current;
        }
    }
}

//2、特性类MyRedisLockAttribute.cs
namespace Demos.MicrosoftRedLockNet.AopLocks
{
    /// <summary>
    /// 分布式事务AOP 
    /// </summary>
    [AttributeUsage(AttributeTargets.Method, Inherited = false)]
    public class MyRedisLockAttribute : Attribute
    {
        /// <summary>
        /// 锁的Key,支持SpEL表达式,例如 "seckill:product:{ProductId}:user:{UserId}"
        /// </summary>
        public string Key { get; set; }
        /// <summary>
        /// 等待锁的超时时间(秒)
        /// </summary>
        public int WaitTime { get; set; } = 3;
        /// <summary>
        /// 锁自动释放时间(秒)
        /// </summary>
        public int LeaseTime { get; set; } = 10;
        /// <summary>
        /// 获取锁失败时的提示信息
        /// </summary>
        public string FailMessage { get; set; } = "系统繁忙,请稍后重试";
    }
}

//3、AOP实现类RedisLockInterceptor.cs
using Castle.DynamicProxy;
using RedLockNet;
using StackExchange.Redis;
using System.Reflection;
namespace Demos.MicrosoftRedLockNet.AopLocks
{
    public class RedisLockInterceptor : IInterceptor
    {
        private readonly IDistributedLockFactory _lockFactory;
        private readonly IDatabase _redisDb;
        public RedisLockInterceptor(IConnectionMultiplexer redisConnection, IDistributedLockFactory lockFactory)
        {
            _lockFactory = lockFactory;
            _redisDb = redisConnection.GetDatabase();
        }
        /// <summary>
        /// 异步方法拦截器
        /// </summary>
        /// <param name="invocation"></param>
        /// <exception cref="NotSupportedException"></exception>
        public void Intercept(IInvocation invocation)
        {
            var method = invocation.Method;
            var returnType = method.ReturnType;


            if (returnType == typeof(Task))
            {
                invocation.ReturnValue = InterceptAsyncVoid(invocation);
            }
            else if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
            {
                var resultType = returnType.GetGenericArguments()[0];
                var genericMethod = GetType()
                    .GetMethod(nameof(InterceptAsyncWithResult), BindingFlags.NonPublic | BindingFlags.Instance)
                    .MakeGenericMethod(resultType);
                invocation.ReturnValue = genericMethod.Invoke(this, new object[] { invocation });
            }
            else
            {
                throw new NotSupportedException("仅支持异步方法 (Task 或 Task<T>)");
            }
        }
        /// <summary>
        /// 处理无返回值的异步方法 (Task)
        /// </summary>
        private async Task InterceptAsyncVoid(IInvocation invocation)
        {
            var attribute = GetMyRedisLockAttribute(invocation);
            if (attribute == null)
            {
                invocation.Proceed();
                await (Task)invocation.ReturnValue;
                return;
            }
            var redLock = await AcquireLockAsync(attribute, invocation);
            if (redLock == null) return;
            try
            {
                invocation.Proceed();
                await (Task)invocation.ReturnValue;
            }
            finally
            {
                redLock.Dispose();
            }
        }
        /// <summary>
        /// 处理有返回值的异步方法 (Task<TResult>)
        /// </summary>
        private async Task<TResult> InterceptAsyncWithResult<TResult>(IInvocation invocation)
        {
            var attribute = GetMyRedisLockAttribute(invocation);
            if (attribute == null)
            {
                invocation.Proceed();
                return await (Task<TResult>)invocation.ReturnValue;
            }
            var redLock = await AcquireLockAsync(attribute, invocation);
            if (redLock == null) return default!; // 正常情况下不会走到这里
            try
            {
                invocation.Proceed();
                return await (Task<TResult>)invocation.ReturnValue;
            }
            finally
            {
                redLock.Dispose();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="attribute"></param>
        /// <param name="invocation"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="Exception"></exception>
        private async Task<IRedLock> AcquireLockAsync(MyRedisLockAttribute attribute, IInvocation invocation)
        {
            var lockKey = LockKeyParser.Parse(
                attribute.Key,
                invocation.Arguments,
                invocation.Method.GetParameters()
            );
            if (string.IsNullOrEmpty(lockKey))
                throw new ArgumentException("RedisLock Key 不能为空");
            var redLock = await _lockFactory.CreateLockAsync(
                lockKey,
                TimeSpan.FromSeconds(attribute.LeaseTime),
                TimeSpan.FromSeconds(attribute.WaitTime),
                TimeSpan.FromSeconds(1)
            );
            if (!redLock.IsAcquired)
                throw new Exception(attribute.FailMessage);
            return redLock;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="invocation"></param>
        /// <returns></returns>
        private MyRedisLockAttribute GetMyRedisLockAttribute(IInvocation invocation)
        {
            var targetMethod = invocation.MethodInvocationTarget ?? invocation.Method;
            return targetMethod.GetCustomAttribute<MyRedisLockAttribute>();
        }
    }
}

3.5、创建控制器OrderController.cs

csharp 复制代码
using Demos.MicrosoftRedLockNet.Services;
using Demos.MicrosoftRedLockNet.Services.Dtos;
using Microsoft.AspNetCore.Mvc;

namespace Demos.MicrosoftRedLockNet.Controllers
{
    /// <summary>
    /// 
    /// </summary>
    [Route("/api/app/[controller]")]
    [ApiController]
    public class OrderController : ControllerBase
    {
        private readonly IOrderService _orderService;
        public OrderController(IOrderService orderService)
        {
            _orderService = orderService;
        }

        /// <summary>
        /// 秒杀商品
        /// </summary>
        /// <returns></returns>
        [HttpPost("seckill")]
        public async Task<IActionResult> Seckill(ShopOrderDto dto)
        {
            try
            {
                var result = await _orderService.CreateOrderAsync(dto);
                Console.WriteLine($"{result.message}");
                return Ok(result);
            }
            catch (Exception ex)
            {
                return BadRequest(ex.Message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        [HttpGet("seckill2")]
        public async Task<IActionResult> Seckill2([FromQuery] ShopOrderDto dto)
        {
            try
            { 
                var  result = await _orderService.CreateOrderAsync(dto);
                Console.WriteLine($"{result.message}");
                return Ok(result);
            }
            catch (Exception ex)
            {
                return BadRequest(ex.Message);
            }
        }
    }
}
//请求参数类
namespace Demos.MicrosoftRedLockNet.Services.Dtos
{
    public class ShopOrderDto
    {
        /// <summary>
        /// 
        /// </summary>
        public long UserId { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public long ProductId {  get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int ProductCount { get; set; }
    }
}
 

4、使用ab模拟并发环境测试

4.1、下载Apache测试工具

通过网盘分享的文件:Apache24(ab压测工具).zip

链接: pan.baidu.com/s/1nVV18oP8... 提取码: 8090, 解压后如下:

4.2、部署nginx,通过nginx负载均衡模拟服务分发

下载地址:通过网盘分享的文件:nginx-1.20.2.zip

链接: pan.baidu.com/s/1lTxJZfWe... 提取码: 8090

nginx配置已经配好,代理本地5001和5002端口,NGINX使用的9021端口

shell 复制代码
#user  nobody;
worker_processes  1;
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
#pid        logs/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;


    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';
    #access_log  logs/access.log  main;
    sendfile        on;
    #tcp_nopush     on;
    #keepalive_timeout  0;
    keepalive_timeout  65;
    #gzip  on;
    # --- 新增:定义后端负载均衡服务器组 ---
    upstream backend_pool {
        # 默认使用轮询 (round-robin) 算法
        server localhost:5001;
        server localhost:5002;
        # 如果需要加权轮询,可以使用 weight 参数,例如:
        # server 127.0.0.1:5001 weight=3;
        # server 127.0.0.1:5002 weight=1;
    }
    server {
        # ---监听端口改为 9021 ---
        listen       9021;
        server_name  localhost;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
        location / {
            proxy_pass http://backend_pool;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            # root   html; 
            # index  index.html index.htm;
        }
        #error_page  404              /404.html;
        # redirect server error pages to the static page /50x.html
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

5、测试

5.1、启动两个webapi实例

shell 复制代码
在项目目录下运行
dotnet run --urls http://localhost:5001

另外再打开一个powershell窗口运行
dotnet run --urls http://localhost:5002

5.3、使用ab命令发送并发请求

shell 复制代码
D:\Itools\Apache24\bin> .\ab.exe -n 100 -c 50 "http://localhost:9021/api/app/order/seckill2?pid=111&uid=889900&count=1"

ab指令说明:
   -n:表示总共100个请求
   -c:表示一次50个并发

返回的库存数量顺序不一致是由于执行库存扣减和写入订单记录后,每次请求的响应时间差异导致,确保最终扣减数量是请求数量,并且没有出现重复的库存说明该分布式锁是正常运行的. 源码来源:gitee.com/inc-zz/netC...,个人开源dotnet基础技术,持续更新中,欢迎Star

相关推荐
步步为营DotNet3 小时前
解锁.NET 11 新能:C# 14 在客户端安全编程的革新与实践
人工智能·microsoft·.net
步步为营DotNet3 小时前
深入.NET 11:ASP.NET Core 10 高并发场景下的性能调优与安全加固
人工智能·microsoft·.net
xifangge20251 天前
彻底解决 .NET 10.0 运行库缺失报错:从 CLR 寻址机制到全版本离线部署实践(附 net运行库合集安装包)
.net
rockey6271 天前
AScript之匿名类型与动态类型
c#·.net·script·eval·expression·动态脚本
一个帅气昵称啊1 天前
.Net基于NetCoreKevin框架 AI 与 Hangfire 集成:实现AI智能自动任务调度
人工智能·.net·hangfire
bjzhang751 天前
Lin CMS .NET Core——一款基于 .NET 8 + FreeSql 实现的前后端分离的 CMS 系统
.net·lin cms
步步为营DotNet1 天前
解锁.NET 11 潜力:Microsoft.Extensions.AI 在后端 AI 集成中的实践与剖析
人工智能·microsoft·.net
wangl_922 天前
初探 C# 15 的 Union Types
java·开发语言·算法·c#·.net·.net core
SEO-狼术2 天前
Make Range Input Simpler
pdf·.net