使用 .NET Core 中的超时中间件提高 UI 性能

科技   2024-11-27 16:34   上海  


今天带来了 .NET Core 的新文章 Timeout 中间件,让我们了解一下,看看我们可以实时应用哪些地方。

实时用例

在实时应用程序(如金融交易平台)中,及时响应至关重要**。如果提供股票价格或执行交易的服务响应时间过长,可能会导致重大财务损失。 超时中间件可用于确保如果这些服务在指定时间范围内没有响应,则请求将中止,并向用户返回错误。**

了解 Timeout 中间件

默认情况下,ASP.NET Core 服务器不会执行此操作,因为请求处理时间因方案而异。例如,WebSockets、静态文件和调用昂贵的 API 都需要不同的超时限制。因此,ASP.NET Core 提供了配置每个终端节点的超时以及全局超时的中间件。

Timeout 中间件的好处

  1. 提高可靠性:确保您的应用程序不会因外部依赖项或内部处理速度缓慢而无限期挂起,从而提高整体可靠性。

  2. 资源管理:防止资源被长时间运行的请求占用,从而释放资源用于其他请求。

  3. 用户体验:通过及时返回响应(即使是错误)来提供更好的用户体验,而不是让用户无限期等待。

  4. 错误处理:允许以集中方式处理请求超时,从而更轻松地记录和管理这些事件。

  5. 安全性:通过限制任何请求消耗服务器资源的时间,减少拒绝服务 (DoS) 攻击的攻击面。

  6. 在下面的文章中了解有关 DoS 攻击的更多信息

何时使用 Timeout 中间件

超时中间件在以下情况下特别有用:

  1. 防止资源耗尽: 确保长时间运行的请求不会无限期地消耗服务器资源。

  2. 提升用户体验: 当请求无法在合理的时间范围内完成时,向用户提供及时的反馈。

  3. 保持应用程序响应能力: 通过终止超过特定持续时间的请求来保持应用程序的响应。

  4. 实施 SLA(服务水平协议): 确保应用程序满足预定义的性能和响应时间标准。

  5. 处理不可预测的负载: 在高流量期间或处理不可预测的工作负载时管理请求时间。

实现

有多种方法可以实现它。

一个。我们可以创建自定义 Middleware 来配置 Timeout

b.我们可以在 Controller 和 Action 级别使用 [RequestTimeout] 属性。

1. 自定义超时中间件

请考虑下面的代码

public class TimeoutClass
{
private readonly RequestDelegate _next;
private readonly TimeSpan _timeout;

public TimeoutClass(RequestDelegate next, TimeSpan timeout)
{
_next = next;
_timeout = timeout;
}

public async Task InvokeAsync(HttpContext context)
{
using (var cts = new CancellationTokenSource(_timeout))
{
try
{
context.RequestAborted = cts.Token;
await _next(context);
}
catch (OperationCanceledException)
{
context.Response.StatusCode = StatusCodes.Status504GatewayTimeout;
await context.Response.WriteAsync("Request timed out happening.");
}
}
}
}

我们创建了 Timeout 类,并通过构造函数注入了 RequestDelegate 和超时时间。

在 Invoke Method 中,我们设置了指定超时的 Cancellation Token。当达到超时限制时,HttpContext.RequestAborted 中的 CancellationToken 会将 IsCancellationRequested 设置为。Abort() 不会在请求中自动调用,因此应用程序可能仍会生成成功或失败响应。如果应用程序不处理异常并生成响应,则默认行为是返回状态代码 504。true

注册 TimeoutClass 中间件

public static class TimeoutMiddlewareExtensions  
{
public static IApplicationBuilder UseTimeoutMiddleware(this IApplicationBuilder builder, TimeSpan timeout)
{
return builder.UseMiddleware<TimeoutClass>(timeout);
}
}

程序

using Microsoft.AspNetCore.Http.Timeouts;  
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseTimeoutMiddleware(TimeSpan.FromSeconds(10));

app.UseAuthorization();

app.MapControllers();

app.Run();

解释

  • Timeout类:此中间件类为每个请求设置超时。如果请求未在指定时间 () 内完成,则会引发 an,并返回 504 Gateway Timeout 响应。_timeoutOperationCanceledException

  • UseTimeoutMiddleware:此扩展方法允许您将超时中间件添加到具有指定超时的中间件管道中。

  • Startup Configuration:将 middleware 添加到方法中的 pipeline,超时时间为 10 秒。根据应用程序的需要调整超时值。Configure

KeyPoint:我们设置了 Timeout =10,表示如果任何请求耗时超过 10 秒,则会调用超时异常。

app.UseTimeoutMiddleware(TimeSpan.FromSeconds(10));

测试 TimeoutClass 中间件。

创建以下 API 端点

namespace TimeoutMiddleware.Controllers
{
[ApiController]
[Route("[controller]")]
public class DemoTimeOutConttoller : ControllerBase
{

[HttpGet("TestTimeOutMiddleware/{delay:int}")]
public async Task<IActionResult> TestTimeOutMiddleware([FromRoute] int delay)
{

// in real time there will be long running http call or long running db call
await Task.Delay(TimeSpan.FromSeconds(delay), HttpContext.RequestAborted);
return Ok();
}

}
}

这里我们从用户输入中传递延迟时间。根据 Code ,我们预计如果延迟时间超过 10 秒,那么我们应该收到超时异常

让我们执行这段代码

我们按预期收到超时异常。

2. [RequestTimeout()] 属性

我们可以在控制器级别或操作级别使用 [RequestTimeout(“1000”)] 属性实现请求超时。

步骤

a. 使用以下代码更新程序.cs

通过调用 AddRequestTimeouts 将请求超时中间件添加到服务集合中。

通过调用 UseRequestTimeout 将中间件添加到请求处理管道。

using Microsoft.AspNetCore.Http.Timeouts;  
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddRequestTimeouts();
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

//app.UseTimeoutMiddleware(TimeSpan.FromSeconds(10));

app.UseAuthorization();
app.UseRequestTimeouts();
app.MapControllers();

app.Run();


b. 在控制者级别申请。

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
[RequestTimeout("00:00:05")] // Timeout of 5 seconds
public class MyController : ControllerBase
{
[HttpGet("TimeOutFunction")]
public async Task<IActionResult> TimeOutFunction()
{
// Simulate a long-running task like long running db call and http call
await Task.Delay(10000); // 10 seconds
return Ok("Operation completed.");
}
}

说明:使用以下属性意味着我们已经在控制器级别配置了超时 =5 秒的所有端点,这意味着如果任何端点的超时时间超过 5 秒,将调用异常。

[RequestTimeout("00:00:05")] // Timeout of 5 seconds

c. 在操作级别


[ApiController]
[Route("api/[controller]")]
public class MyController : ControllerBase
{
[HttpGet("TwoSecondTimeout")]
[RequestTimeout("00:00:02")] // Timeout of 2 seconds
public async Task<IActionResult> TwoSecondTimeout()
{
// Simulate a task that finishes quickly
await Task.Delay(1000); // 1 second
return Ok("Done with work.");
}

[HttpGet("FiveSecondTimeout")]
[RequestTimeout("00:00:05")] // Timeout of 5 seconds
public async Task<IActionResult> FiveSecondTimeout()
{
// Simulate a long-running task
await Task.Delay(10000); // 10 seconds
return Ok("work completed.");
}
}

我们在操作级别应用了此属性,就像我们为每个端点配置了自己的超时期限一样。

3. 为 Minimal API 配置超时

对于最小的 API 应用程序,通过调用 WithRequestTimeout 或应用属性将终端节点配置为超时,如以下示例所示:[RequestTimeout]

using Microsoft.AspNetCore.Http.Timeouts;  

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRequestTimeouts();

var app = builder.Build();
app.UseRequestTimeouts();

//WAY 1
app.MapGet("/TwoSecondTimeout", async (HttpContext context) => {
try
{
await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
}
catch (TaskCanceledException)
{
return Results.Content("Timeout!", "text/plain");
}

return Results.Content("No timeout!", "text/plain");
}).WithRequestTimeout(TimeSpan.FromSeconds(2));
// Returns "Timeout!"


//WAY 2 with Attribure

app.MapGet("/TwoSecondTimeout",
[RequestTimeout(milliseconds: 2000)\] async (HttpContext context) => {
try
{
await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
}
catch (TaskCanceledException)
{
return Results.Content("Timeout!", "text/plain");
}

return Results.Content("No timeout!", "text/plain");
});
// Returns "Timeout!"

app.Run();

4. 其他场景

假设我们在应用程序中有 10 个终端节点,并且我们想配置 4 个具有 5 秒超时的终端节点和 6 个具有 15 秒超时的终端节点,那么我们可以定义如下策略

builder.Services.AddRequestTimeouts(options => {  
options.DefaultPolicy =
new RequestTimeoutPolicy { Timeout = TimeSpan.FromMilliseconds(1500) };
options.AddPolicy("TwoSecondPolicy", TimeSpan.FromSeconds(2));
});app.MapGet("/namedpolicy", async (HttpContext context) => {
try
{
await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
}
catch (TaskCanceledException)
{
return Results.Content("Timeout!", "text/plain");
}

return Results.Content("No timeout!", "text/plain");
}).WithRequestTimeout("TwoSecondPolicy");
// Returns "Timeout!"

应用程序中所有端点的全局超时设置

   // Adding timeout middleware  
app.Use(async (context, next) =>
{
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10));
context.RequestAborted = cancellationTokenSource.Token;

try
{
await next();
}
catch (OperationCanceledException)
{
context.Response.StatusCode = StatusCodes.Status408RequestTimeout;
}
});

如果你喜欢我的文章,请给我一个赞!谢谢

架构师老卢
资深软件架构师, 分享编程、软件设计经验, 教授前沿技术, 分享技术资源(每天发布电子书),每天进步一点点...
 最新文章