Feat Rate Limit para cuotear peticiones.
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="System.Threading.RateLimiting" Version="9.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using Polly;
|
||||
using Polly.Extensions.Http;
|
||||
using System.Threading.RateLimiting;
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Console()
|
||||
@@ -81,6 +82,26 @@ builder.Services.AddHttpClient("ElectoralApiClient", client =>
|
||||
|
||||
.AddPolicyHandler(GetRetryPolicy());
|
||||
|
||||
// --- LIMITADOR DE VELOCIDAD BASADO EN TOKEN BUCKET ---
|
||||
builder.Services.AddSingleton<RateLimiter>(sp =>
|
||||
new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
|
||||
{
|
||||
// El tamaño máximo del cubo (la ráfaga máxima que permitimos).
|
||||
TokenLimit = 50,
|
||||
|
||||
// Con qué frecuencia se añaden nuevas fichas al cubo.
|
||||
ReplenishmentPeriod = TimeSpan.FromSeconds(1),
|
||||
|
||||
// Cuántas fichas se añaden en cada período.
|
||||
TokensPerPeriod = 20,
|
||||
|
||||
// Cuántas peticiones pueden estar en cola esperando una ficha.
|
||||
QueueLimit = 1000,
|
||||
|
||||
// Cómo se comporta cuando la cola está llena.
|
||||
QueueProcessingOrder = QueueProcessingOrder.OldestFirst
|
||||
}));
|
||||
|
||||
builder.Services.AddScoped<IElectoralApiService, ElectoralApiService>();
|
||||
|
||||
builder.Services.AddHostedService<Worker>();
|
||||
|
||||
@@ -41,38 +41,54 @@ public class Worker : BackgroundService
|
||||
_logger.LogInformation("Iniciando sondeo periódico de resultados...");
|
||||
_logger.LogInformation("-------------------------------------------------");
|
||||
|
||||
int cicloContador = 0;
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
var cicloInicio = DateTime.UtcNow;
|
||||
cicloContador++;
|
||||
|
||||
var authToken = await _apiService.GetAuthTokenAsync();
|
||||
if (string.IsNullOrEmpty(authToken))
|
||||
{
|
||||
_logger.LogError("CRÍTICO: No se pudo obtener el token de autenticación. Reintentando en 1 minuto...");
|
||||
_logger.LogError("CRÍTICO: No se pudo obtener el token. Reintentando en 1 minuto...");
|
||||
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- CAMBIO CLAVE: DE PARALELO A SECUENCIAL ---
|
||||
// Se elimina Task.WhenAll y se ejecutan las tareas una después de la otra.
|
||||
// Esto previene los errores de Gateway Timeout (504).
|
||||
_logger.LogInformation("--- Iniciando sondeo de Resultados Municipales ---");
|
||||
// --- CICLO CALIENTE: TAREAS DE ALTA PRIORIDAD (SIEMPRE SE EJECUTAN) ---
|
||||
_logger.LogInformation("--- Iniciando Ciclo Caliente #{ciclo} ---", cicloContador);
|
||||
|
||||
await SondearResultadosMunicipalesAsync(authToken, stoppingToken);
|
||||
|
||||
_logger.LogInformation("--- Iniciando sondeo de Resumen Provincial ---");
|
||||
await SondearResumenProvincialAsync(authToken, stoppingToken);
|
||||
|
||||
_logger.LogInformation("--- Iniciando sondeo de Estado de Recuento General ---");
|
||||
await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken);
|
||||
|
||||
_logger.LogInformation("--- Iniciando sondeo de Proyección de Bancas ---");
|
||||
await SondearProyeccionBancasAsync(authToken, stoppingToken);
|
||||
// --- CICLO FRÍO: TAREAS DE BAJA PRIORIDAD (SE EJECUTAN CADA 5 CICLOS) ---
|
||||
// El operador '%' (módulo) nos dice si el contador es divisible por 5.
|
||||
if (cicloContador % 5 == 1) // Se ejecuta en el ciclo 1, 6, 11, etc.
|
||||
{
|
||||
_logger.LogInformation("--- Iniciando Ciclo Frío (Bancas y Telegramas) ---");
|
||||
await SondearProyeccionBancasAsync(authToken, stoppingToken);
|
||||
await SondearNuevosTelegramasAsync(authToken, stoppingToken);
|
||||
}
|
||||
|
||||
//_logger.LogInformation("--- Iniciando sondeo de Nuevos Telegramas ---");
|
||||
//await SondearNuevosTelegramasAsync(authToken, stoppingToken);
|
||||
var cicloFin = DateTime.UtcNow;
|
||||
var duracionCiclo = cicloFin - cicloInicio;
|
||||
_logger.LogInformation("Ciclo #{ciclo} completado en {duration} segundos.", cicloContador, duracionCiclo.TotalSeconds);
|
||||
|
||||
// --- ESPERA INTELIGENTE ---
|
||||
// Esperamos lo que quede para completar 1 minuto desde el inicio del ciclo.
|
||||
// Si el ciclo tardó 20 segundos, esperamos 40. Si tardó más de 1 minuto, la espera es mínima.
|
||||
var tiempoDeEspera = TimeSpan.FromMinutes(1) - duracionCiclo;
|
||||
if (tiempoDeEspera < TimeSpan.Zero)
|
||||
{
|
||||
tiempoDeEspera = TimeSpan.FromSeconds(5); // Una espera mínima si el ciclo se excedió
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Ciclo de sondeo completado. Esperando 5 minutos para el siguiente...");
|
||||
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
||||
_logger.LogInformation("Esperando {wait_seconds} segundos para el siguiente ciclo...", tiempoDeEspera.TotalSeconds);
|
||||
await Task.Delay(tiempoDeEspera, stoppingToken);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Worker")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+a4e47b6e3d1f8b0746f4f910f56a94e17b2e030c")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+68dce9415e165633856e4fae9b2d71cc07b4e2ff")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Worker")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Worker")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -203,6 +203,10 @@
|
||||
"Microsoft.Extensions.Http": {
|
||||
"target": "Package",
|
||||
"version": "[9.0.8, )"
|
||||
},
|
||||
"System.Threading.RateLimiting": {
|
||||
"target": "Package",
|
||||
"version": "[9.0.8, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
@@ -309,6 +313,10 @@
|
||||
"Serilog.Sinks.File": {
|
||||
"target": "Package",
|
||||
"version": "[7.0.0, )"
|
||||
},
|
||||
"System.Threading.RateLimiting": {
|
||||
"target": "Package",
|
||||
"version": "[9.0.8, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
|
||||
Reference in New Issue
Block a user