十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

在分布式系统和高并发 API 设计中,自动化请求幂等缓存(Automated Request Idempotency Cache)是防止由于网络抖动、超时重试或用户重复点击导致“重复扣款”、“ 在分布式系统和高并发 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 响应结果]关键细节处理防并发Locking/Processing使用 Redis 的SET NX指令设置短暂锁或Processing状态防止瞬间高并发击穿。异常回滚如果业务代码执行抛出异常非业务失败需要清除缓存中的Processing标记允许客户端发起重试。超时时间TTL幂等键必须设置合理过期时间如 24 小时释放内存空间。三、 C# (.NET 8) 完整实现案例通过 ASP.NET Core 的ActionFilter过滤器或Middleware中间件我们可以无侵入地实现“自动化幂等”。下面使用自定义特性AttributeIAsyncActionFilterIDistributedCacheRedis示范。1. 定义幂等特性 (IdempotentAttribute)用于标记在 Controller 的 Action 上支持指定过期时间。usingMicrosoft.AspNetCore.Mvc.Filters;[AttributeUsage(AttributeTargets.Method|AttributeTargets.Class)]publicclassIdempotentAttribute:Attribute{publicintExpireSeconds{get;}/// param nameexpireSeconds幂等结果缓存时长秒默认 86400 秒 (24小时)/parampublicIdempotentAttribute(intexpireSeconds86400){ExpireSecondsexpireSeconds;}}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;privateconststringHeaderKeyNameX-Idempotency-Key;publicIdempotencyFilter(IDistributedCachecache){_cachecache;}publicasyncTaskOnActionExecutionAsync(ActionExecutingContextcontext,ActionExecutionDelegatenext){// 1. 检查 Endpoint 是否贴有 [Idempotent] 特性varidempotentAttrcontext.ActionDescriptor.EndpointMetadata.OfTypeIdempotentAttribute().FirstOrDefault();if(idempotentAttrnull){awaitnext();return;}// 2. 从 HTTP Header 提取 Idempotency-Keyif(!context.HttpContext.Request.Headers.TryGetValue(HeaderKeyName,outvarkeyValues)||string.IsNullOrWhiteSpace(keyValues.FirstOrDefault())){context.ResultnewBadRequestObjectResult(new{error$Missing required header:{HeaderKeyName}});return;}stringidempotencyKey$idempotency:{keyValues.FirstOrDefault()};// 3. 读取缓存状态varcachedDataawait_cache.GetStringAsync(idempotencyKey);if(!string.IsNullOrEmpty(cachedData)){varentryJsonSerializer.DeserializeIdempotencyCacheEntry(cachedData);if(entry?.StatusProcessing){// 正在并发处理中拒绝重复触发context.ResultnewConflictObjectResult(new{errorConcurrent request in progress. Please retry later.});return;}if(entry?.StatusCompleted){// 回放历史响应varcontentResultnewContentResult{StatusCodeentry.StatusCode,ContentTypeentry.ContentType??application/json,Contententry.ResultJson};context.ResultcontentResult;return;}}// 4. 尝试抢占 “Processing” 锁状态通过简单的 Redis 占位生产环境推荐使用 RedLockvarprocessingEntryJsonSerializer.Serialize(newIdempotencyCacheEntry{StatusProcessing});// 假设预留 30 秒的处理超时时间await_cache.SetStringAsync(idempotencyKey,processingEntry,newDistributedCacheEntryOptions{AbsoluteExpirationRelativeToNowTimeSpan.FromSeconds(30)});// 5. 执行真实业务逻辑ActionExecutedContextexecutedContext;try{executedContextawaitnext();}catch{// 如果执行出现未捕获异常清除幂等 Key允许客户端重试await_cache.RemoveAsync(idempotencyKey);throw;}// 6. 业务成功执行完后捕获结果并写入最终缓存if(executedContext.ResultisObjectResultobjectResult){varcompletedEntrynewIdempotencyCacheEntry{StatusCompleted,StatusCodeobjectResult.StatusCode??200,ContentTypeapplication/json,ResultJsonJsonSerializer.Serialize(objectResult.Value)};await_cache.SetStringAsync(idempotencyKey,JsonSerializer.Serialize(completedEntry),newDistributedCacheEntryOptions{AbsoluteExpirationRelativeToNowTimeSpan.FromSeconds(idempotentAttr.ExpireSeconds)});}}}4. 注册与应用在Program.cs中配置依赖注入varbuilderWebApplication.CreateBuilder(args);// 注册 Distributed Cache生产环境中建议替换为 AddStackExchangeRedisCachebuilder.Services.AddDistributedMemoryCache();// 注册全局或局部Filterbuilder.Services.AddScopedIdempotencyFilter();builder.Services.AddControllers(options{// 全局开启幂等过滤器options.Filters.AddIdempotencyFilter();});varappbuilder.Build();app.MapControllers();app.Run();5. Controller 使用示例在需要保证幂等的 API如下单、扣款上加上[Idempotent][ApiController][Route(api/[controller])]publicclassOrdersController:ControllerBase{[HttpPost][Idempotent(expireSeconds:86400)]// 启用幂等结果缓存 24 小时publicIActionResultCreateOrder([FromBody]CreateOrderDtorequest){// 模拟高耗时业务逻辑varorderIdGuid.NewGuid().ToString(N);returnOk(new{Successtrue,OrderIdorderId,Amountrequest.Amount,CreatedAtDateTime.UtcNow});}}publicrecordCreateOrderDto(decimalAmount);四、 生产落地实践避坑指南Idempotency-Key 生成策略客户端生成推荐由前端/客户端使用 UUID 生成并放在 Header如X-Idempotency-Key。服务端提取摘要若客户端无法配合可以由中间件将User ID Request Method Path Request Body Hash进行 MD5/SHA256 计算作为缓存 Key。只对写操作POST/PUT/PATCH开启GET / DELETE 原则上在 HTTP 协议规范中本身就是幂等或无副作用的不需要过度加锁。HTTP 状态码过滤只有在业务返回2xx或某些明确的4xx如参数校验失败时才缓存Completed结果如果服务器内部发生5xx错误通常不应该缓存应当允许重试。
返回列表