提问人:Rakesh Kumar 提问时间:11/3/2023 最后编辑:Peter CsalaRakesh Kumar 更新时间:11/5/2023 访问量:60
我应该使用 IMemoryCache 来存储持有者令牌,还是针对 Azure 函数中的 401 错误实现 Polly 重试策略?
Should I use IMemoryCache to store the bearer token or implement a Polly retry policy for 401 errors in Azure Function?
问:
我正在使用服务总线主题触发器 Azure 函数,在向外部 API 发出 HTTP 请求时,我需要处理 401(未经授权)错误,例如刷新持有者令牌并重试失败的请求。
我正在考虑两种方法,但不确定哪种方法更合适。 我将不胜感激有关哪种方法最好的指导。
这是我的重试策略类,其中包含 401 错误的未经授权的重试策略。
public class PollyRetryPolicy : IPollyRetryPolicy
{
private readonly ILogger<PollyRetryPolicy> _logger;
private readonly IOptions<RetryPolicyConfigOptions> _options;
public PollyRetryPolicy(ILogger<PollyRetryPolicy> logger,
IOptions<RetryPolicyConfigOptions> options)
{
_logger = logger;
_options = options;
_jitterer = new Random();
}
public IAsyncPolicy GetUnauthorizedRetryPolicy()
{
var policy = Policy
.Handle<HttpRequestException>(ex => ex.StatusCode == HttpStatusCode.Unauthorized)
.WaitAndRetryAsync(
_options.Value.MaxRetryAttempts,
retryAttempt => TimeSpan.Zero); // Set the delay to zero milliseconds for immediate retry
return policy;
}
}
这是我的 api 客户端,我在其中尝试调用第一个持有者令牌终结点并使用 IMemoryCache 存储持有者令牌,然后使用此持有者令牌进行实际调用。
public class NotificationApiClient : INotificationApiClient
{
private readonly ILogger<NotificationApiClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IMemoryCache _memoryCache;
private readonly NotificationApiConfigOption _notificationApiConfigOption;
public NotificationApiClient(ILogger<NotificationApiClient> logger,
IHttpClientFactory httpClientFactory,
IMemoryCache memoryCache,
IOptions<NotificationApiConfigOption> notificationApiConfigOption)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
_memoryCache = memoryCache;
_notificationApiConfigOption = notificationApiConfigOption.Value;
}
public async Task<bool> CheckDeliveryAccessAsync(int code)
{
try
{
var httpClient = _httpClientFactory.CreateClient();
httpClient.BaseAddress = new Uri(_notificationApiConfigOption.BaseUri);
// Get the bearer token
string bearerToken = await GetNotificationAccessTokenAsync();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
var apiResponse = await httpClient.GetAsync($"{_notificationApiConfigOption.CheckDeliveryAccessApiUri}?code={code}");
if (apiResponse.StatusCode == HttpStatusCode.OK)
{
string resultContent = await apiResponse.Content.ReadAsStringAsync();
var result = resultContent.AsPoco<CheckInvoiceDeliveryAccessResponse>();
return result.Data.IsActive;
}
else
{
return false;
}
}
catch (Exception ex)
{
throw;
}
}
public async Task<string> GetNotificationAccessTokenAsync()
{
try
{
if (_memoryCache.TryGetValue("BearerToken", out string cachedBearerToken))
{
return cachedBearerToken;
}
var httpClient = _httpClientFactory.CreateClient();
httpClient.BaseAddress = new Uri(_notificationApiConfigOption.BaseUri);
var formData = new Dictionary<string, string>
{
{ "clientId", _notificationApiConfigOption.ClientId },
{ "clientSecret", _notificationApiConfigOption.ClientSecret }
};
var content = new FormUrlEncodedContent(formData);
var apiResponse = await httpClient.PostAsync($"/{_notificationApiConfigOption.AccessTokenUri}", content);
if (apiResponse.StatusCode == HttpStatusCode.OK)
{
string resultContent = await apiResponse.Content.ReadAsStringAsync();
var result = resultContent.AsPoco<NotificationAccessTokenResponse>();
_memoryCache.Set("BearerToken", result.AccessToken, new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(result.ExpiresIn)
});
return result.AccessToken;
}
else
{
return string.Empty;
}
}
catch (Exception ex)
{
throw;
}
}
}
答:
1赞
Peter Csala
11/3/2023
#1
如评论部分所述@codebrane,您还可以在新令牌过期之前主动检索新令牌。
主动和被动方法都有其优点和缺点。如果您的服务面向客户,我建议使用主动方法。使用此方法时,不应因服务令牌过期而重试用户请求。
如果服务之间的通信不是连续的,而是按需进行的,则响应式方法可能很有用。重试施加的延迟是可以接受的。
在这里,我详细介绍了 3 种不同的实现方法: 将 Polly 与 Named Client 结合使用 Refresh Token
关于这件作品的一点说明:
var policy = Policy
.Handle<HttpRequestException>(ex => ex.StatusCode == HttpStatusCode.Unauthorized)
.WaitAndRetryAsync(
_options.Value.MaxRetryAttempts,
retryAttempt => TimeSpan.Zero); // Set the delay to zero milliseconds for immediate retry
如果您不想在重试尝试之间等待,则可以使用RetryAsync
var policy = Policy
.Handle<HttpRequestException>(ex => ex.StatusCode == HttpStatusCode.Unauthorized)
.RetryAsync(_options.Value.MaxRetryAttempts);
更新 #1
我的应用程序不是面向客户的,它是一种后台工作,可以根据事件触发通知。所以在这种情况下,我应该使用主动还是应该使用被动?
一如既往,视情况而定。:)让我试着帮助你进行权衡分析。
反应式方法
优点
- 它是按需触发的。如果没有流量,则不执行令牌刷新
- 如果您的系统具有反应性,那么对这种方法进行推理就更容易了
缺点
- 如果令牌检索/刷新通常比原始请求花费的时间更长,则增加的延迟(由于令牌服务调用)变得有形
- 如果令牌在突发期间过期,则许多请求将暂停(排队),直到新令牌可用
积极主动的方法
优点
- 从传入请求处理的角度来看,您的系统的行为就像您拥有长期存期的令牌一样
- 根据令牌服务实现,它可能允许一次检索多个令牌(每个下游服务一个令牌)
缺点
- 如果令牌服务是速率限制/强加配额的,那么这种方法比反应式方法更有可能超过限制
- 可能还需要使用重试来修饰令牌服务调用,以克服暂时性故障
旁注:在这两种情况下,您都必须确保没有并发检索调用。换言之,您只能通过单个线程刷新令牌。通过积极主动的方法更容易保证这一点。
评论
0赞
Rakesh Kumar
11/5/2023
谢谢!彼得的建议。我的应用程序不是面向客户的,它是一种后台工作,可以根据事件触发通知。所以在这种情况下,我应该使用主动还是应该使用被动?
0赞
Peter Csala
11/5/2023
@RakeshKumar我已经更新了我的帖子以反映您的问题。请检查一下!
1赞
Rakesh Kumar
11/5/2023
谢谢!很多解释:)现在我明白了。
上一个:如何按索引从列表中删除元素
评论