经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Redis » 查看文章
ASP.NET Core使用filter和redis实现接口防重
来源:cnblogs  作者:Chadz  时间:2023/3/17 9:00:26  对本文有异议

背景

日常开发中,经常需要对一些响应不是很快的关键业务接口增加防重功能,即短时间内收到的多个相同的请求,只处理一个,其余不处理,避免产生脏数据。这和幂等性(idempotency)稍微有点区别,幂等性要求的是对重复请求有相同的效果结果,通常需要在接口内部执行业务操作前检查状态;而防重可以认为是一个业务无关的通用功能,在ASP.NET Core中我们可以借助Filter和redis实现。

关于Filter

Filter的由来可以追溯到ASP.NET MVC中的ActionFilter和ASP.NET Web API中的ActionFilterAttribute。ASP.NET Core将这些不同类型的Filter统一为一种类型,称为Filter,以简化API和提高灵活性。ASP.NET Core中Filter可以用于实现例如身份验证、日志记录、异常处理、性能监控等各种功能。

image

通过使用Filter,我们可以在请求处理管道的特定阶段之前或者之后运行自定义代码,达到AOP的效果。

image

编码实现

防重组件的思路很简单,将第一次请求的某些参数作为标识符存入redis中,并设置过期时间,下次请求过来,先检查redis相同的请求是否已被处理;
作为一个通用组件,我们需要能让使用者自定义作为标识符的字段以及过期时间,下面开始实现。

PreventDuplicateRequestsActionFilter

  1. public class PreventDuplicateRequestsActionFilter : IAsyncActionFilter
  2. {
  3. public string[] FactorNames { get; set; }
  4. public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; }
  5. private readonly IDistributedCache _cache;
  6. private readonly ILogger<PreventDuplicateRequestsActionFilter> _logger;
  7. public PreventDuplicateRequestsActionFilter(IDistributedCache cache, ILogger<PreventDuplicateRequestsActionFilter> logger)
  8. {
  9. _cache = cache;
  10. _logger = logger;
  11. }
  12. public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
  13. {
  14. var factorValues = new string?[FactorNames.Length];
  15. var isFromBody =
  16. context.ActionDescriptor.Parameters.Any(r => r.BindingInfo?.BindingSource == BindingSource.Body);
  17. if (isFromBody)
  18. {
  19. var parameterValue = context.ActionArguments.FirstOrDefault().Value;
  20. factorValues = FactorNames.Select(name =>
  21. parameterValue?.GetType().GetProperty(name)?.GetValue(parameterValue)?.ToString()).ToArray();
  22. }
  23. else
  24. {
  25. for (var index = 0; index < FactorNames.Length; index++)
  26. {
  27. if (context.ActionArguments.TryGetValue(FactorNames[index], out var factorValue))
  28. {
  29. factorValues[index] = factorValue?.ToString();
  30. }
  31. }
  32. }
  33. if (factorValues.All(string.IsNullOrEmpty))
  34. {
  35. _logger.LogWarning("Please config FactorNames.");
  36. await next();
  37. return;
  38. }
  39. var idempotentKey = $"{context.HttpContext.Request.Path.Value}:{string.Join("-", factorValues)}";
  40. var idempotentValue = await _cache.GetStringAsync(idempotentKey);
  41. if (idempotentValue != null)
  42. {
  43. _logger.LogWarning("Received duplicate request({},{}), short-circuiting...", idempotentKey, idempotentValue);
  44. context.Result = new AcceptedResult();
  45. }
  46. else
  47. {
  48. await _cache.SetStringAsync(idempotentKey, DateTimeOffset.UtcNow.ToString(),
  49. new DistributedCacheEntryOptions {AbsoluteExpirationRelativeToNow = AbsoluteExpirationRelativeToNow});
  50. await next();
  51. }
  52. }
  53. }

PreventDuplicateRequestsActionFilter里,我们首先通过反射从 ActionArguments拿到指定参数字段的值,由于从request body取值略有不同,我们需要分开处理;接下来开始拼接key并检查redis,如果key已经存在,我们需要短路请求,这里直接返回的是 Accepted (202)而不是Conflict (409)或者其它错误状态,是为了避免上游已经调用失败而继续重试。

PreventDuplicateRequestsAttribute

防重组件的全部逻辑在PreventDuplicateRequestsActionFilter中已经实现,由于它需要注入 IDistributedCacheILogger对象,我们使用IFilterFactory实现一个自定义属性,方便使用。

  1. [AttributeUsage(AttributeTargets.Method)]
  2. public class PreventDuplicateRequestsAttribute : Attribute, IFilterFactory
  3. {
  4. private readonly string[] _factorNames;
  5. private readonly int _expiredMinutes;
  6. public PreventDuplicateRequestsAttribute(int expiredMinutes, params string[] factorNames)
  7. {
  8. _expiredMinutes = expiredMinutes;
  9. _factorNames = factorNames;
  10. }
  11. public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
  12. {
  13. var filter = serviceProvider.GetService<PreventDuplicateRequestsActionFilter>();
  14. filter.FactorNames = _factorNames;
  15. filter.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(_expiredMinutes);
  16. return filter;
  17. }
  18. public bool IsReusable => false;
  19. }

注册

为了简单,操作redis,直接使用微软Microsoft.Extensions.Caching.StackExchangeRedis包;注册PreventDuplicateRequestsActionFilterPreventDuplicateRequestsAttribute无需注册。

  1. builder.Services.AddStackExchangeRedisCache(options =>
  2. {
  3. options.Configuration = "127.0.0.1:6379,DefaultDatabase=1";
  4. });
  5. builder.Services.AddScoped<PreventDuplicateRequestsActionFilter>();

使用

假设我们有一个接口CancelOrder,我们指定入参中的OrderId和Reason为因子。

  1. namespace PreventDuplicateRequestDemo.Controllers
  2. {
  3. [Route("api/[controller]")]
  4. [ApiController]
  5. public class OrderController : ControllerBase
  6. {
  7. [HttpPost(nameof(CancelOrder))]
  8. [PreventDuplicateRequests(5, "OrderId", "Reason")]
  9. public async Task<IActionResult> CancelOrder([FromBody] CancelOrderRequest request)
  10. {
  11. await Task.Delay(1000);
  12. return new OkResult();
  13. }
  14. }
  15. public class CancelOrderRequest
  16. {
  17. public Guid OrderId { get; set; }
  18. public string Reason { get; set; }
  19. }
  20. }

启动程序,多次调用api,除第一次调用成功,其余请求皆被短路
image

查看redis,已有记录
image

参考链接

https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-7.0
https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-7.0

原文链接:https://www.cnblogs.com/netry/p/aspnetcore-prevent-duplicate-requests-filter-redis.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号