在分布式系统和高并发 API 设计中,自动化请求幂等缓存(Automated Request Idempotency Cache)是防止由于网络抖动、超时重试或用户重复点击导致“重复扣款”、“
2026/9/4 15:44:13 网站建设 项目流程

在分布式系统和高并发 API 设计中,自动化请求幂等缓存(Automated Request Idempotency Cache)是防止由于网络抖动、超时重试或用户重复点击导致“重复扣款”、“重复创建订单”等副作用的关键机制。


一、 什么是请求幂等与幂等缓存?

  • 幂等(Idempotency):无论对同一个接口发起 1 次还是NNN次相同的请求,系统产生的副作用(Side Effects)和最终状态都是完全一致的。
  • 自动化请求幂等缓存:通过拦截器/中间件对请求进行自动化捕获,根据请求的幂等键(Idempotency Key)去集中式缓存(如 Redis)中查询:
  • 首次请求:标记为“处理中”,执行业务逻辑,将结果存入缓存并返回。
  • 重复请求:若发现相同的 Key 正在处理或已处理完成,直接拦截并回放缓存中的处理结果,不再重复执行业务代码。

二、 核心工作流程与状态转移

一个严谨的自动化幂等缓存机制包含 3 种状态:未处理 (NotExist)处理中 (Processing)已完成 (Completed)

[客户端请求] │ ▼ [提取/生成 Idempotency-Key] ────────────────┐ │ │ ▼ ▼ [查询 Redis 状态] ───────────────► [无 Key / 过期] ───► [加锁 / 设置为 Processing] ─► [执行业务逻辑] │ │ ├──► [状态: Processing] ───► 返回 409 Conflict 或等待 (防止并发重复触发) │ │ │ └──► [状态: Completed] ────► 直接返回缓存的 HttpResponse (回放结果) ▼ [写入 Completed + 响应结果]

关键细节处理:

  1. 防并发(Locking/Processing):使用 Redis 的SET NX指令设置短暂锁或Processing状态,防止瞬间高并发击穿。
  2. 异常回滚:如果业务代码执行抛出异常(非业务失败),需要清除缓存中的Processing标记,允许客户端发起重试。
  3. 超时时间(TTL):幂等键必须设置合理过期时间(如 24 小时),释放内存空间。

三、 C# (.NET 8) 完整实现案例

通过 ASP.NET Core 的ActionFilter(过滤器)Middleware(中间件),我们可以无侵入地实现“自动化幂等”。下面使用自定义特性(Attribute)+IAsyncActionFilter+IDistributedCache(Redis)示范。

1. 定义幂等特性 (IdempotentAttribute)

用于标记在 Controller 的 Action 上,支持指定过期时间。

usingMicrosoft.AspNetCore.Mvc.Filters;[AttributeUsage(AttributeTargets.Method|AttributeTargets.Class)]publicclassIdempotentAttribute:Attribute{publicintExpireSeconds{get;}/// <param name="expireSeconds">幂等结果缓存时长(秒),默认 86400 秒 (24小时)</param>publicIdempotentAttribute(intexpireSeconds=86400){ExpireSeconds=expireSeconds;}}

2. 定义缓存模型 (IdempotencyCacheEntry)

记录 HTTP 响应的状态码和 Response Body。

publicclassIdempotencyCacheEntry{publicstringStatus{get;set;}="Processing";// Processing | CompletedpublicintStatusCode{get;set;}publicstring?ContentType{get;set;}publicstring?ResultJson{get;set;}}

3. 实现自动化幂等过滤器 (IdempotencyFilter)

usingSystem.Text;usingSystem.Text.Json;usingMicrosoft.AspNetCore.Mvc;usingMicrosoft.AspNetCore.Mvc.Filters;usingMicrosoft.Extensions.Caching.Distributed;publicclassIdempotencyFilter:IAsyncActionFilter{privatereadonlyIDistributedCache_cache;privateconststringHeaderKeyName="X-Idempotency-Key";publicIdempotencyFilter(IDistributedCachecache){_cache=cache;}publicasyncTaskOnActionExecutionAsync(ActionExecutingContextcontext,ActionExecutionDelegatenext){// 1. 检查 Endpoint 是否贴有 [Idempotent] 特性varidempotentAttr=context.ActionDescriptor.EndpointMetadata.OfType<IdempotentAttribute>().FirstOrDefault();if(idempotentAttr==null){awaitnext();return;}// 2. 从 HTTP Header 提取 Idempotency-Keyif(!context.HttpContext.Request.Headers.TryGetValue(HeaderKeyName,outvarkeyValues)||string.IsNullOrWhiteSpace(keyValues.FirstOrDefault())){context.Result=newBadRequestObjectResult(new{error=$"Missing required header:{HeaderKeyName}"});return;}stringidempotencyKey=$"idempotency:{keyValues.FirstOrDefault()}";// 3. 读取缓存状态varcachedData=await_cache.GetStringAsync(idempotencyKey);if(!string.IsNullOrEmpty(cachedData)){varentry=JsonSerializer.Deserialize<IdempotencyCacheEntry>(cachedData);if(entry?.Status=="Processing"){// 正在并发处理中,拒绝重复触发context.Result=newConflictObjectResult(new{error="Concurrent request in progress. Please retry later."});return;}if(entry?.Status=="Completed"){// 回放历史响应varcontentResult=newContentResult{StatusCode=entry.StatusCode,ContentType=entry.ContentType??"application/json",Content=entry.ResultJson};context.Result=contentResult;return;}}// 4. 尝试抢占 “Processing” 锁状态(通过简单的 Redis 占位,生产环境推荐使用 RedLock)varprocessingEntry=JsonSerializer.Serialize(newIdempotencyCacheEntry{Status="Processing"});// 假设预留 30 秒的处理超时时间await_cache.SetStringAsync(idempotencyKey,processingEntry,newDistributedCacheEntryOptions{AbsoluteExpirationRelativeToNow=TimeSpan.FromSeconds(30)});// 5. 执行真实业务逻辑ActionExecutedContextexecutedContext;try{executedContext=awaitnext();}catch{// 如果执行出现未捕获异常,清除幂等 Key,允许客户端重试await_cache.RemoveAsync(idempotencyKey);throw;}// 6. 业务成功执行完后,捕获结果并写入最终缓存if(executedContext.ResultisObjectResultobjectResult){varcompletedEntry=newIdempotencyCacheEntry{Status="Completed",StatusCode=objectResult.StatusCode??200,ContentType="application/json",ResultJson=JsonSerializer.Serialize(objectResult.Value)};await_cache.SetStringAsync(idempotencyKey,JsonSerializer.Serialize(completedEntry),newDistributedCacheEntryOptions{AbsoluteExpirationRelativeToNow=TimeSpan.FromSeconds(idempotentAttr.ExpireSeconds)});}}}

4. 注册与应用

Program.cs中配置依赖注入:

varbuilder=WebApplication.CreateBuilder(args);// 注册 Distributed Cache(生产环境中建议替换为 AddStackExchangeRedisCache)builder.Services.AddDistributedMemoryCache();// 注册全局或局部Filterbuilder.Services.AddScoped<IdempotencyFilter>();builder.Services.AddControllers(options=>{// 全局开启幂等过滤器options.Filters.Add<IdempotencyFilter>();});varapp=builder.Build();app.MapControllers();app.Run();

5. Controller 使用示例

在需要保证幂等的 API(如下单、扣款)上加上[Idempotent]

[ApiController][Route("api/[controller]")]publicclassOrdersController:ControllerBase{[HttpPost][Idempotent(expireSeconds:86400)]// 启用幂等,结果缓存 24 小时publicIActionResultCreateOrder([FromBody]CreateOrderDtorequest){// 模拟高耗时业务逻辑varorderId=Guid.NewGuid().ToString("N");returnOk(new{Success=true,OrderId=orderId,Amount=request.Amount,CreatedAt=DateTime.UtcNow});}}publicrecordCreateOrderDto(decimalAmount);

四、 生产落地实践避坑指南

  1. Idempotency-Key 生成策略:
  • 客户端生成(推荐):由前端/客户端使用 UUID 生成并放在 Header(如X-Idempotency-Key)。
  • 服务端提取摘要:若客户端无法配合,可以由中间件将User ID + Request Method + Path + Request Body Hash进行 MD5/SHA256 计算作为缓存 Key。
  1. 只对写操作(POST/PUT/PATCH)开启:GET / DELETE 原则上在 HTTP 协议规范中本身就是幂等或无副作用的,不需要过度加锁。
  2. HTTP 状态码过滤:只有在业务返回2xx或某些明确的4xx(如参数校验失败)时才缓存Completed结果;如果服务器内部发生5xx错误,通常不应该缓存,应当允许重试。

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

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

立即咨询