.NET 10 WebAPI + Redis 分布式锁:解决高并发超卖与重复请求
2026/9/7 4:14:42 网站建设 项目流程

.NET 10 正式版发布后,很多后端团队开始把 WebAPI 集群化改造提上日程。单机阶段写一个lock(obj)就能解决的并发冲突,一旦部署多个实例、前面再挂上负载均衡,锁就直接失效。库存被扣成负数、同一笔订单被创建两次、用户重复点击提交按钮出现多条订单——这些问题不会因为你换语言而消失。本文围绕一套可落地的架构:.NET 10 WebAPI + Redis 实现分布式锁,重点解决集群并发冲突、超卖和重复请求问题,并给出从项目创建到接口压测的完整教学路径。

这套方案的核心特点可以概括为:不依赖具体部署环境,Windows/Linux 都能跑;不追求过度设计,锁的抽象只有 Acquire、Release、Renew 三个方法;不改业务主流程,只在关键临界区加锁;附带幂等防重设计,双击提交、网关重试都能兜住。全程会使用 Redis 7 + StackExchange.Redis 作为底层依赖,代码尽量保持短、可读、可直接进入业务改造成。

文章包含四部分实操:一是 .NET 10 WebAPI 项目创建与 Redis 环境准备;二是基于 StackExchange.Redis 手写分布式锁,含安全释放和看门狗续期;三是把锁应用到库存扣减和订单防重接口;四是接口压测和常见问题排查。全程提供可复制的命令和代码,不是只讲概念。

适合正在做 .NET WebAPI 高并发改造的中高级开发,也适合准备分布式锁面试题的同学。如果你是刚学 .NET 的初学者,建议先把 Redis 基本命令和 WebAPI 依赖注入机制过一遍再上手。

1. 核心能力速览

能力项说明
技术栈.NET 10 WebAPI + Redis 7 + StackExchange.Redis
目标问题集群并发下的超卖、重复请求、库存扣减冲突
核心机制Redis 分布式锁(SET NX EX 原子加锁 + Lua 安全释放 + 看门狗续期)
运行平台Windows / Linux / macOS 开发环境,生产建议 Linux 容器
GPU 依赖不需要
硬性门槛本机已安装 .NET 10 SDK,Redis 服务可达
部署方式dotnet 命令行发布,或打包 Docker 镜像;Redis 独立部署
接口能力WebAPI 返回 JSON,支持 Swagger、curl、Postman、Vue 前端联调
批量任务支持 ab / JMeter 并发压测;业务侧可配合消息队列做削峰
适合场景订单、库存、优惠券、积分等写多读少的高并发业务

先泼一盆冷水:如果只是写一个 CRUD 接口,这套东西属于过度设计。但如果你正在做集群化改造,订单和库存接口要扛住几十上百的瞬时并发,那分布式锁就是绕不开的基础设施。下面直接进入部署和编码。

2. 适用场景与业务边界

2.1 适合解决的业务问题

第一类问题是“库存/优惠券/积分扣减”。典型流程是先查库存,再判断是否足够,最后扣减。单机环境下这段逻辑用lock或数据库行锁就能控制,到了多实例部署,不同实例上的请求同时操作同一商品时,Redis 分布式锁可以提供互斥入口。

第二类问题是“订单幂等防重”。用户双击提交、前端重试、API 网关超时重发,都会让同一个业务请求到达后端多次。治理思路不是让后端“别接到重复请求”,而是让后端识别这是同一个请求,并且只处理第一次。Redis 的SET NX EX天然适合做这个幂等标记。

第三类问题是“分布式定时任务互斥”。多实例部署后,定时任务会在每个实例上都触发一次,如果没有互斥机制,数据会被重复处理。用一个 Redis 锁,谁拿到锁谁执行,能避免大部分重复任务问题。

2.2 不适合什么场景

如果并发量本身不高,或者业务是纯查询、读多写少,完全没有必要引入分布式锁。Redis 锁引入之后还多一个依赖点,Redis 抖动、网络超时、锁过期时间设置不当反而会拖垮业务。

还要提醒一点:Redis 分布式锁解决的是“多实例互斥”,它不能替代数据库的原子更新。库存扣减这类操作,即使有了分布式锁,数据库侧仍然要写UPDATE ... SET stock = stock - @quantity WHERE id = @id AND stock >= @quantity。锁负责让同一时刻只有一个实例进入临界区,数据库条件更新负责兜底防超卖,两者配合,而不是二选一。

2.3 合规与安全边界

涉及订单、用户 ID、商品等真实业务数据时,需要注意个人信息保护和数据合规。生产环境的 Redis 必须开启密码认证,并做好网络隔离,不能把 6379 端口直接暴露到公网。锁使用的 key 命名要规范,避免和缓存 key 混在一起。后续示例代码中的 user id、订单号都是测试数据,落地到生产要按实际业务字段调整。

3. .NET 10 WebAPI 环境准备与项目创建

3.1 安装 .NET 10 SDK

.NET 10 是微软在 2025 年 11 月发布的长期支持版本。开发机需要安装 .NET 10 SDK。

如果使用 Visual Studio 2022,需要将版本更新到支持 .NET 10 的最新版本。很多同学卡在这一步:VS 装的是旧版本,打开项目后提示模板不可用。我的建议是,不管用不用 VS,先把dotnet --version跑通,确保命令行工具可用。

dotnet --version

能正常输出版本号,再继续下一步。

3.2 创建 WebAPI 项目

用命令行创建带 Controller 的 WebAPI 项目,比在 VS 里点模板更不容易踩雷:

dotnet new webapi -n HighConcurrencyDemo --use-controllers cd HighConcurrencyDemo

如果模板提示参数不存在,执行dotnet new webapi --help查看当前 SDK 支持的是--use-controllers还是-controllers,按实际参数调整即可。

项目创建后,添加 Redis 客户端依赖:

dotnet add package StackExchange.Redis

3.3 注册 Redis 连接

打开Program.cs,注册IConnectionMultiplexer单例。这里不使用每请求创建连接的方式,ConnectionMultiplexer本身就是为多路复用设计的,一个全局实例足够:

using StackExchange.Redis; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var redisConnectionString = builder.Configuration.GetConnectionString("Redis"); builder.Services.AddSingleton<IConnectionMultiplexer>( ConnectionMultiplexer.Connect(redisConnectionString)); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseAuthorization(); app.MapControllers(); app.Run();

appsettings.json里配置 Redis 连接字符串:

{ "ConnectionStrings": { "Redis": "127.0.0.1:6379,password=yourpassword,abortConnect=false,connectTimeout=3000" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" }

abortConnect=false的作用是:Redis 暂时不可用时,应用能正常启动,不会直接把整个 WebAPI 进程拖死。但生产环境仍然要先确保 Redis 可用,再启动业务服务。

4. Redis 本地安装与连接配置

4.1 Windows 下怎么装 Redis

Redis 官方并不直接支持 Windows。在 Windows 开发环境下,比较推荐的方案有三个:

  • Docker Desktop 运行 Redis 容器,最省事;
  • WSL2 里安装 Linux 版 Redis;
  • 使用 Memurai 等 Windows 兼容实现。

不推荐在 Windows 下从源码编译 Redis,编译依赖多,后续升级也麻烦。开发阶段直接用 Docker 最合适。

4.2 Docker 启动 Redis 7

使用 Redis 7 镜像启动一个带密码、开启 AOF 持久化的实例:

docker run -d \ --name redis-highconcurrency \ -p 6379:6379 \ -v redis-highconcurrency-data:/data \ redis:7-alpine \ redis-server --appendonly yes --requirepass yourpassword

端口使用默认的 6379。生产环境建议把密码放到配置文件里,不要裸写在命令行,命令行里的启动参数容易被进程列表看到。

4.3 验证 Redis 连通性

redis-cli验证连接:

redis-cli -h 127.0.0.1 -p 6379 -a yourpassword ping

返回PONG说明连接正常。

在进入代码之前,先用 Redis 命令手工验证一下锁的原子操作,理解 NX 和 EX 的组合含义:

redis-cli -h 127.0.0.1 -p 6379 -a yourpassword SET inventory:lock:SKU-1001 request-token NX EX 30 GET inventory:lock:SKU-1001 DEL inventory:lock:SKU-1001

SET key value NX EX 30的含义是:只有当 key 不存在时才写入,并且设置 30 秒过期时间。第一个客户端执行成功后,第二个客户端再执行同样命令会失败,这就是分布式锁的最小语义。

实际开发过程中,可以用 Another Redis Desktop Manager 这类可视化工具查看 key 的变化,检查锁是否异常残留、TTL 是否符合预期,排查效率会高很多。

5. Redis 分布式锁核心实现:原子加锁、安全释放与看门狗

5.1 为什么单机 lock 不可用

多实例部署后,一个请求被负载均衡分配到实例 A,另一个请求被分配到实例 B。lock(obj)锁的是进程内对象,实例 A 的锁对实例 B 没有任何约束力。分布式锁的本质是把“互斥状态”放到所有实例都能访问的公共存储上,Redis 就是这样一个公共存储。

5.2 锁的抽象接口

先把锁抽象成三个方法,这样可以很自然地把 Redis 实现和业务逻辑解耦:

public interface IDistributedLock { Task<bool> AcquireAsync(string lockKey, string requestId, TimeSpan expiry); Task<bool> ReleaseAsync(string lockKey, string requestId); Task<bool> RenewAsync(string lockKey, string requestId, TimeSpan expiry); }

requestId必须由调用方每次生成一个唯一值,通常用Guid.NewGuid().ToString("N")。它的作用是标识“这把锁是谁加的”,释放锁时只有持有者才能释放,避免误删别人的锁。

5.3 Redis 实现:加锁、释放、续期

下面是基于 StackExchange.Redis 的完整实现:

using StackExchange.Redis; public class RedisDistributedLock : IDistributedLock { private readonly IConnectionMultiplexer _redis; public RedisDistributedLock(IConnectionMultiplexer redis) { _redis = redis; } public async Task<bool> AcquireAsync(string lockKey, string requestId, TimeSpan expiry) { var db = _redis.GetDatabase(); return await db.StringSetAsync(lockKey, requestId, expiry, When.NotExists); } public async Task<bool> ReleaseAsync(string lockKey, string requestId) { var db = _redis.GetDatabase(); var script = @" if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end "; var result = await db.ScriptEvaluateAsync( script, new RedisKey[] { lockKey }, new RedisValue[] { requestId }); return (long)result! == 1; } public async Task<bool> RenewAsync(string lockKey, string requestId, TimeSpan expiry) { var db = _redis.GetDatabase(); var script = @" if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('pexpire', KEYS[1], ARGV[2]) else return 0 end "; var result = await db.ScriptEvaluateAsync( script, new RedisKey[] { lockKey }, new RedisValue[] { requestId, expiry.TotalMilliseconds }); return (long)result! == 1; } }

三个关键点要理解到位:

第一,加锁为什么用When.NotExists。这个枚举对应 Redis 的NX参数,保证“判断 key 不存在 + 写入”是一个原子操作,不存在并发窗口。

第二,释放锁为什么要写 Lua 脚本。释放之前先GET比较 value 是不是自己的requestId,再DEL。如果不用 Lua,而是先GETDEL,两步之间存在时间差:当前线程的锁到期,另一个线程拿到锁,当前线程再执行DEL,就把别人的锁删掉了。用 Lua 脚本可以把“判断 + 删除”封装成原子操作。

第三,为什么需要续期。如果锁过期时间设置成 10 秒,但业务执行了 20 秒,第二个线程在第一个线程业务还没结束时就拿到了锁,冲突依旧。续期的作用是:业务还活着,锁就一直延长有效期。

5.4 看门狗续期思路

看门狗是一个后台任务,周期性对当前进程持有的锁做续期。用 BackgroundService 可以实现,核心思路是维护一个“当前持有锁”的内存注册表:

public class LockWatchdog : BackgroundService { private readonly IDistributedLock _distributedLock; private readonly TimeSpan _expiry = TimeSpan.FromSeconds(30); private readonly TimeSpan _renewInterval = TimeSpan.FromSeconds(10); // 业务代码通过 RegisterAsync / UnregisterAsync 维护当前持有的锁集合 protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { await Task.Delay(_renewInterval, stoppingToken); // 遍历注册表中所有锁,逐个调用 RenewAsync } } }

这是简化示意。生产实现需要用ConcurrentDictionary管理锁的requestId和到期时间,并且业务结束时从注册表移除。如果不想手写,业界也有 RedLock.net 等现成库,但需要评估其实现是否符合你的场景。官方文档也明确警告过:RedLock 本身并不是绝对安全的锁方案,在 Redis 主从切换、网络分区等场景下依然存在锁丢失的可能。我的建议是:先自己实现一套短小精悍的锁,把原理吃透,再决定用不用现成库。

6. 业务接口实战:库存扣减、订单幂等与重复请求拦截

6.1 库存扣减接口

库存接口是超卖问题最典型的发生点。锁的目的是让同一商品同一时刻只有一个请求进入扣减流程,但数据库条件更新仍然要保留。

using Microsoft.AspNetCore.Mvc; using StackExchange.Redis; [ApiController] [Route("api/inventory")] public class InventoryController : ControllerBase { private readonly IDistributedLock _distributedLock; private readonly IConnectionMultiplexer _redis; private readonly ILogger<InventoryController> _logger; public InventoryController( IDistributedLock distributedLock, IConnectionMultiplexer redis, ILogger<InventoryController> logger) { _distributedLock = distributedLock; _redis = redis; _logger = logger; } [HttpPost("deduct")] public async Task<IActionResult> Deduct(DeductRequest request) { var lockKey = $"inventory:lock:{request.ProductId}"; var requestId = Guid.NewGuid().ToString("N"); var expiry = TimeSpan.FromSeconds(10); var acquired = await _distributedLock.AcquireAsync(lockKey, requestId, expiry); if (!acquired) { return StatusCode(409, new { code = 409, message = "系统繁忙,请稍后重试" }); } try { var db = _redis.GetDatabase(); var stockKey = $"inventory:stock:{request.ProductId}"; var currentStock = (int)await db.StringGetAsync(stockKey); if (currentStock < request.Quantity) { return BadRequest(new { code = 400, message = "库存不足" }); } await db.StringDecrementAsync(stockKey, request.Quantity); // 真正落地库存时,必须再执行数据库原子更新: // UPDATE product SET stock = stock - @quantity // WHERE id = @productId AND stock >= @quantity // Redis 锁只进入一层互斥,数据库条件更新才是最终兜底 return Ok(new { code = 200, data = new { remaining = currentStock - request.Quantity } }); } finally { await _distributedLock.ReleaseAsync(lockKey, requestId); } } public record DeductRequest(string ProductId, int Quantity); }

这个案例演示的锁用法是“临界区保护”:加锁成功后,先查 Redis 里的库存,判断是否足够,再扣减。实际生产环境,库存数据的主存储肯定在关系型数据库,Redis 缓存可能只是热点数据加速。无论数据放在哪一层,都要记住:锁负责互斥,数据库的条件更新负责最终一致性。

如果锁超时导致业务还在执行,但锁已经释放,另一个请求进入临界区,最终数据库条件更新WHERE stock >= quantity也能拦住超卖。这就是前面说的双保险。

6.2 订单幂等防重接口

幂等防重的思路和加锁不一样。加锁解决的是“同时抢资源”,幂等解决的是“同一个请求只处理一次”。最简单的做法是:客户端每次请求携带一个幂等键,比如IdempotencyKey或业务订单号,服务端用 Redis 判断这个键是否已经处理过。

[ApiController] [Route("api/orders")] public class OrderController : ControllerBase { private readonly IDistributedLock _distributedLock; private readonly IConnectionMultiplexer _redis; public OrderController( IDistributedLock distributedLock, IConnectionMultiplexer redis) { _distributedLock = distributedLock; _redis = redis; } [HttpPost("create")] public async Task<IActionResult> CreateOrder(OrderCreateRequest request) { var idempotencyKey = request.IdempotencyKey ?? Guid.NewGuid().ToString("N"); var db = _redis.GetDatabase(); var idempotencyKeyName = $"idempotency:order:{idempotencyKey}"; bool isFirstRequest = await db.StringSetAsync( idempotencyKeyName, "processing", TimeSpan.FromHours(24), When.NotExists); if (!isFirstRequest) { return Ok(new { code = 200, message = "订单已处理,无需重复提交", orderId = await db.StringGetAsync($"idempotency:orderId:{idempotencyKey}") }); } var lockKey = $"order:lock:{request.UserId}"; var requestId = Guid.NewGuid().ToString("N"); var acquired = await _distributedLock.AcquireAsync(lockKey, requestId, TimeSpan.FromSeconds(10)); if (!acquired) { return StatusCode(409, new { code = 409, message = "订单处理中,请勿重复提交" }); } try { // 真正创建订单的逻辑 // 创建成功后,把订单 ID 写入 Redis,后续重复请求直接返回 var orderId = Guid.NewGuid().ToString("N"); await db.StringSetAsync($"idempotency:orderId:{idempotencyKey}", orderId); await db.StringSetAsync(idempotencyKeyName, "completed", TimeSpan.FromHours(24)); return Ok(new { code = 200, orderId = orderId }); } catch { // 业务失败要删除幂等标记,否则用户永远无法重试 await db.KeyDeleteAsync(idempotencyKeyName); throw; } finally { await _distributedLock.ReleaseAsync(lockKey, requestId); } } public record OrderCreateRequest(string UserId, string IdempotencyKey, int Quantity); }

这里两把锁的功能不同。幂等标记负责拦截重复请求,分布式锁负责同一用户并发创建订单时的互斥。实际项目中,如果前面有 API 网关并且网关已经做了幂等去重,后端可以只保留一层。但从接口自身健壮性考虑,两层的抗压能力会明显更好。

注意一个细节:业务失败时要把幂等标记删掉,否则用户修正参数后还是会被当成“重复请求”拦截,导致永远无法下单。这种问题在自测阶段很难发现,通常要到联调阶段才能暴露。

6.3 锁过期时间怎么定

锁的过期时间不是拍脑袋写的,判断依据是“临界区最大可能执行时长”。如果业务一般 200 毫秒内完成,可以设置 3 到 5 秒;如果涉及多个远程调用,可以把过期时间放宽到 10 到 30 秒。过期时间太短,业务没跑完锁就释放;太长,Redis 宕机时锁的阻塞时间也会更长。稳妥做法是设置一个合理的默认值,再配合看门狗续期。

7. 接口 API 测试、压测与性能验证

7.1 启动服务并访问 Swagger

执行以下命令启动服务:

dotnet run

浏览器访问/swagger,能看到api/inventory/deductapi/orders/create两个接口。先用 Swagger 手动点击测试,确认参数校验和返回值正常,再上压测。

7.2 curl 调用接口

实际联调时,前端是 Vue 或者小程序的情况下,用 curl 验证最直接:

curl -X POST "http://localhost:5000/api/inventory/deduct" \ -H "Content-Type: application/json" \ -d '{"productId":"SKU-1001","quantity":1}'

正常返回:

{ "code": 200, "data": { "remaining": 0 } }

库存用完后,再次请求会返回库存不足的提示。这里为了测试方便,可以先在 Redis 里手动初始化库存 key:

redis-cli -h 127.0.0.1 -p 6379 -a yourpassword SET inventory:stock:SKU-1001 10

7.3 并发压测观察超卖与重复请求

使用 ApacheBench 做最简单的小并发压测:

ab -n 500 -c 20 -p order.json -T application/json \ http://localhost:5000/api/orders/create

order.json内容:

{ "userId": "user-10086", "idempotencyKey": "idempotency-test-001", "quantity": 1 }

保持idempotencyKey不变,重复执行压测,理想结果是:无论请求打多少次,后端只创建一单。这个场景验证的就是幂等防重能力。如果想看抢库存场景,可以把idempotencyKey改成随机值,然后观察库存从 10 扣到 0 的过程,整个过程不能出现扣成负数、不能出现超卖。

压测关注三个指标:错误率、接口耗时、库存或订单数量是否正确。如果压测过程中出现大量 409,说明锁冲突明显;如果出现超卖,优先查数据库条件更新是否写对;如果出现重复订单,优先查幂等标记是否生效。

7.4 用 Redis monitor 观察锁过程

压测时打开另外一个终端,用 Redis monitor 实时观察命令:

redis-cli -h 127.0.0.1 -p 6379 monitor

能看到锁的 SET、释放锁的 Lua 脚本调用、库存扣减命令。这个操作对排查锁是否生效非常直观。生产环境不要长期开 monitor,它会对 Redis 性能产生明显影响,只在测试阶段使用。

7.5 观察 .NET 进程资源占用

压测过程中,可以用dotnet-counters观察进程的 GC 和线程池状态:

dotnet-counters monitor --process-id <进程ID> --counters System.Runtime

如果耗时集中在 Redis 操作上,优先确认ConnectionMultiplexer是否注册为单例。很多初学者在每次请求里ConnectionMultiplexer.Connect,压测一上来就会把连接数打爆,性能表现会很差。正确的做法就是第 3 章那样,在依赖注入容器里注册单例。

8. 常见问题与排查方法

问题现象可能原因排查方式解决方案
启动后 Redis 连接失败地址或密码错误、Redis 未启动执行redis-cli ping修改连接串,确认 Redis 容器状态
接口持续返回系统繁忙锁未释放、锁过期时间过短、死锁用 Redis 可视化工具查看锁 key 的 TTL检查 finally 释放逻辑,调整过期时间,增加看门狗
释放锁误删他人锁释放时没有校验 requestId查看释放脚本是否存在 GET + DEL 分步执行必须用 Lua 脚本实现比较后删除
压测后库存出现负数只用了 Redis 锁,数据库条件更新缺失排查 SQL 是否有stock >= quantity数据库 UPDATE 增加条件,锁和条件更新双保险
幂等接口业务失败后无法重试异常时没有删除幂等标记查看 Redis 中idempotency:*keycatch 中删除幂等标记
Redis 锁 key 和缓存 key 冲突key 命名未加前缀查看 key 列表锁 key 统一加lock:前缀
服务重启后锁一直存在加锁时没有设置过期时间查看锁 key 的 TTL 是否为 -1使用SET NX EX设置过期时间
压测时 Redis 连接数暴涨每请求都创建 ConnectionMultiplexer查看连接数和日志注册单例复用连接
Redis 主从切换后锁丢失锁写入 master 后未同步到 slave查看主从日志,评估锁方案部署 Redis 哨兵或 Cluster,评估 RedLock 的适用性
Redis 连接超时网络抖动、Redis 负载过高查看 Redis 慢日志和网络延迟增加超时时间,使用连接池,控制客户端重试

9. 最佳实践与使用建议

9.1 锁的使用规范

分布式锁不是越重越好。尽可能缩短临界区范围,只在真正需要互斥的代码段加锁,避免把日志、外部 HTTP 调用、文件写入都放在锁内,否则锁的持有时间会被无限拉长。

锁的命名要有规范。推荐格式是业务:资源类型:资源ID:lock,例如order:user:10086:lock。带上业务前缀,可以避免不同业务共用同一个 Redis 实例时 key 互相污染。

锁的 value 必须是唯一标识。所有示例都使用Guid.NewGuid().ToString("N"),这是分布式系统里的通用做法。没有这个标识,就无法安全释放锁。

9.2 三层防御体系

从整个高并发后端架构来看,Redis 分布式锁只是其中一环。更完整的防御体系是三层的:

第一层,Redis 锁或幂等标记,解决并发进入和重复请求的互斥问题; 第二层,数据库条件更新,比如UPDATE ... WHERE stock >= quantity,解决真正的超卖问题; 第三层,消息队列和批量任务削峰,防止短时冲击把接口打垮。

这套思路对 .NET 和 Java 是相通的。很多团队还在纠结 .NET 和 Java 怎么选,其实高并发分布式锁、缓存、消息队列这些技术在中后台架构上是同构的,先把一条链路跑通,再做选型更有意义。

9.3 AI 辅助开发的正确用法

AI 辅助开发在这类项目中可以明显提速,但要用对地方。

可以让 AI 生成 WebAPI 接口骨架、Swagger 注释、DTO 定义、单元测试、压测脚本,这些内容模式化、重复度高,交给 AI 很合适。也可以把报错日志贴给 AI,让它给出初步排查方向,节省查资料的时间。

但分布式锁的核心代码,包括 Lua 脚本、锁的释放逻辑、看门狗续期,强烈建议自己手动写一遍,并且逐行理解。原因很直接:这些代码属于“一旦出错后果严重”的底层基础设施,靠 AI 生成容易拿到表面能用、边界情况全错的版本。面试时考分布式锁,考官想听的是你对锁语义和原子性的理解,不是你有没有调过某个库。

10. 总结与下一步

这套基于 .NET 10 WebAPI + Redis 的分布式锁骨架,最值得尝试的地方是把“锁”抽象成了三个方法:加锁、释放、续期。业务代码只需要关心 key 和 requestId,不需要每次重新实现 Redis 原子操作。这个抽象可以在 RedLock、自研锁、数据库锁之间随时切换。

第一步建议先验证的功能:启动 Redis,初始化一个库存 key,然后用ab -n 500 -c 20压测库存扣减接口。看两个结果:库存没有被扣成负数,重复请求没有产生多笔订单。如果这两个结果都正确,集群并发冲突的最核心问题就已经解决了。

最容易踩的坑有两个:一个是锁过期太快导致业务没跑完锁就释放,另一个是释放锁时没有校验 requestId 导致误删别人的锁。前者靠看门狗续期缓解,后者靠 Lua 脚本兜底。这两个点也是分布式锁面试题的高频考点,可以重点准备。

后续可以继续扩展的方向包括:接入 Redis 哨兵或 Cluster 解决单点问题;用消息队列做订单创建削峰;给接口增加限流组件;把幂等逻辑封装成中间件或 ActionFilter,避免每个接口重复写一遍。整个架构从“能用”到“生产可用”,中间还需要补日志、监控、告警这些工程化能力,先把最小骨架跑通,再逐步补齐。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询