调整代码以使用 built-in RateLimit 政策
Adjusting code to use the built-in RateLimit policy
我想通过 built-in RateLimit policy 实现与以下策略相同的行为,即记录器消息并读取 Retry-After header 并等待需要等待但使用 built-in RateLimit 策略的确切秒数。
尝试
// TODO: No logger message and not sure if it waits the time taken from the Retry-After header.
public static AsyncRateLimitPolicy Limit<T>(ILogger<T> logger)
{
return Policy.RateLimitAsync(RateLimitRetryCount, TimeSpan.FromSeconds(5));
}
有效
public static AsyncRetryPolicy<RestResponse> AsyncRateLimit<T>(ILogger<T> logger)
{
return Policy.HandleResult<RestResponse>(response => response.StatusCode == HttpStatusCode.TooManyRequests)
.WaitAndRetryAsync(RateLimitRetryCount,
(attemptCount, restResponse, _) =>
{
var retryAfterHeader = restResponse?.Result?.Headers?.SingleOrDefault(h => h.Name == "Retry-After");
double secondsInterval = 0;
if (retryAfterHeader != null)
{
var value = retryAfterHeader.Value?.ToString();
if (!double.TryParse(value, out secondsInterval))
{
secondsInterval = Math.Pow(2, attemptCount);
}
}
return TimeSpan.FromSeconds(secondsInterval);
},
(response, timeSpan, retryCount, _) =>
{
logger.LogTrace(
"The API request has been rate limited. HttpStatusCode={StatusCode}. Waiting {Seconds} seconds before retry. Number attempt {RetryCount}. Uri={Url}; RequestResponse={Content}",
response.Result.StatusCode, timeSpan.TotalSeconds, retryCount, response.Result.ResponseUri, response.Result.Content);
return Task.CompletedTask;
});
}
有多个问题,让我一一回答。
1) 如何将记录器注入策略?
为此你需要使用 Polly's context。
上下文是在策略之外创建的。它被用作存储任意信息的容器
var context = new Context().WithLogger(logger);
然后通过Execute
/ExecuteAsync
调用传递
await policy.ExecuteAsync(ctx => FooAsync(), context);
最后,您可以在任何用户委托(如 onRetry
/onRetryAsync
)中使用上下文来检索传递的 object
(exception, timeSpan, retryCount, context) =>
{
var logger = context.GetLogger();
logger?.LogWarning(...);
...
}
WithLogger
和 GetLogger
扩展方法
public static class ContextExtensions
{
private static readonly string LoggerKey = "LoggerKey";
public static Context WithLogger(this Context context, ILogger logger)
{
context[LoggerKey] = logger;
return context;
}
public static ILogger GetLogger(this Context context)
{
if (context.TryGetValue(LoggerKey, out object logger))
{
return logger as ILogger;
}
return null;
}
}
2) 上述限速器的工作方式是否与重试相同?
没有。速率限制器是一种 主动 策略,可用于防止资源滥用。这意味着如果超过预定义的限制,它将抛出 RateLimitRejectedException
。
每当我们谈论弹性策略时,我们指的是双方之间的预定义协议,以克服瞬态故障。所以限速器是这个故事的 server-side 而重试(反应策略)是 client-side.
如果你想在你的速率限制器中设置 RetryAfter
header 那么你可以这样做
IAsyncPolicy<HttpResponseMessage> limit = Policy
.RateLimitAsync(RateLimitRetryCount, TimeSpan.FromSeconds(5), RateLimitRetryCount,
(retryAfter, context) => {
var response = new HttpResponseMessage(System.Net.HttpStatusCode.TooManyRequests);
response.Headers.Add("Retry-After", retryAfter.TotalSeconds.ToString());
return response;
});
然后在重试的 sleepDurationProvider
委托中的 client-side 上,如果 response
是 DelegateResult<HttpResponseMessage>
,您可以像这样检索该值
response.Result.Headers.RetryAfter.Delta ?? TimeSpan.FromSeconds(0)
我想通过 built-in RateLimit policy 实现与以下策略相同的行为,即记录器消息并读取 Retry-After header 并等待需要等待但使用 built-in RateLimit 策略的确切秒数。
尝试
// TODO: No logger message and not sure if it waits the time taken from the Retry-After header.
public static AsyncRateLimitPolicy Limit<T>(ILogger<T> logger)
{
return Policy.RateLimitAsync(RateLimitRetryCount, TimeSpan.FromSeconds(5));
}
有效
public static AsyncRetryPolicy<RestResponse> AsyncRateLimit<T>(ILogger<T> logger)
{
return Policy.HandleResult<RestResponse>(response => response.StatusCode == HttpStatusCode.TooManyRequests)
.WaitAndRetryAsync(RateLimitRetryCount,
(attemptCount, restResponse, _) =>
{
var retryAfterHeader = restResponse?.Result?.Headers?.SingleOrDefault(h => h.Name == "Retry-After");
double secondsInterval = 0;
if (retryAfterHeader != null)
{
var value = retryAfterHeader.Value?.ToString();
if (!double.TryParse(value, out secondsInterval))
{
secondsInterval = Math.Pow(2, attemptCount);
}
}
return TimeSpan.FromSeconds(secondsInterval);
},
(response, timeSpan, retryCount, _) =>
{
logger.LogTrace(
"The API request has been rate limited. HttpStatusCode={StatusCode}. Waiting {Seconds} seconds before retry. Number attempt {RetryCount}. Uri={Url}; RequestResponse={Content}",
response.Result.StatusCode, timeSpan.TotalSeconds, retryCount, response.Result.ResponseUri, response.Result.Content);
return Task.CompletedTask;
});
}
有多个问题,让我一一回答。
1) 如何将记录器注入策略?
为此你需要使用 Polly's context。
上下文是在策略之外创建的。它被用作存储任意信息的容器
var context = new Context().WithLogger(logger);
然后通过Execute
/ExecuteAsync
调用传递
await policy.ExecuteAsync(ctx => FooAsync(), context);
最后,您可以在任何用户委托(如 onRetry
/onRetryAsync
)中使用上下文来检索传递的 object
(exception, timeSpan, retryCount, context) =>
{
var logger = context.GetLogger();
logger?.LogWarning(...);
...
}
WithLogger
和 GetLogger
扩展方法
public static class ContextExtensions
{
private static readonly string LoggerKey = "LoggerKey";
public static Context WithLogger(this Context context, ILogger logger)
{
context[LoggerKey] = logger;
return context;
}
public static ILogger GetLogger(this Context context)
{
if (context.TryGetValue(LoggerKey, out object logger))
{
return logger as ILogger;
}
return null;
}
}
2) 上述限速器的工作方式是否与重试相同?
没有。速率限制器是一种 主动 策略,可用于防止资源滥用。这意味着如果超过预定义的限制,它将抛出 RateLimitRejectedException
。
每当我们谈论弹性策略时,我们指的是双方之间的预定义协议,以克服瞬态故障。所以限速器是这个故事的 server-side 而重试(反应策略)是 client-side.
如果你想在你的速率限制器中设置 RetryAfter
header 那么你可以这样做
IAsyncPolicy<HttpResponseMessage> limit = Policy
.RateLimitAsync(RateLimitRetryCount, TimeSpan.FromSeconds(5), RateLimitRetryCount,
(retryAfter, context) => {
var response = new HttpResponseMessage(System.Net.HttpStatusCode.TooManyRequests);
response.Headers.Add("Retry-After", retryAfter.TotalSeconds.ToString());
return response;
});
然后在重试的 sleepDurationProvider
委托中的 client-side 上,如果 response
是 DelegateResult<HttpResponseMessage>
response.Result.Headers.RetryAfter.Delta ?? TimeSpan.FromSeconds(0)