如何让HttpClient请求排队等待Polly重试策略完成Token刷新?
问题场景与现有实现
我正在使用Polly处理HTTP请求返回401错误时的授权Token刷新,现有方案基于Polly结合命名客户端实现,相关代码如下:
重试策略代码
static IAsyncPolicy<HttpResponseMessage> GetAuthRefreshPolicy(IServiceProvider serviceProvider) { return Policy<HttpResponseMessage> .HandleResult(response => response.StatusCode == HttpStatusCode.Unauthorized) .RetryAsync(async (handler, retry) => await serviceProvider.GetRequiredService<IAuthorisationService>().RefreshTokenAsync()); }
授权服务代码
public class AuthorisationService : IAuthorisationService { public String? AuthToken { get; private set; } private readonly IAuthenticationClient _AuthenticationClient; private readonly AuthOptions _AuthOptions; public AuthorisationService(IAuthenticationClient authenticationClient, IOptions<AuthOptions> options) { _AuthenticationClient = authenticationClient; _AuthOptions = options.Value; } public async Task RefreshTokenAsync() { this.AuthToken = await _AuthenticationClient.LoginAsync(_AuthOptions.User, _AuthOptions.Password); } }
存在的问题
当授权Token无效且多个请求同时发起时,由于异步执行特性,所有请求都会使用无效Token触发Polly重试策略,导致多次不必要的Token刷新请求。
希望实现:某个请求因401失败后,后续所有请求等待Token刷新成功后再执行;或者将重试尝试排队,若Token已刷新则跳过刷新请求。同时想了解是否可以用Polly的舱壁断路器等特性,或者更简单的Token刷新锁定方式。
解决方案
方法一:授权服务添加异步锁定(最直接简单)
给AuthorisationService添加SemaphoreSlim实现互斥刷新,确保同一时间只有一个请求执行Token刷新,其他请求等待刷新完成后直接复用新Token:
public class AuthorisationService : IAuthorisationService { public String? AuthToken { get; private set; } private readonly IAuthenticationClient _AuthenticationClient; private readonly AuthOptions _AuthOptions; private readonly SemaphoreSlim _refreshLock = new SemaphoreSlim(1, 1); // 初始化信号量,仅允许1个并发刷新操作 public AuthorisationService(IAuthenticationClient authenticationClient, IOptions<AuthOptions> options) { _AuthenticationClient = authenticationClient; _AuthOptions = options.Value; } public async Task RefreshTokenAsync() { await _refreshLock.WaitAsync(); try { // 双重检查:等待期间若已有其他请求完成刷新,直接返回 if (!string.IsNullOrEmpty(AuthToken)) { // 可选:添加Token有效性校验,避免极端场景下的过期问题 return; } this.AuthToken = await _AuthenticationClient.LoginAsync(_AuthOptions.User, _AuthOptions.Password); } finally { _refreshLock.Release(); // 释放锁,允许其他请求继续 } } }
这个方案无需修改Polly策略,仅改造授权服务即可解决问题:
- 第一个触发401的请求会获取锁并执行刷新
- 后续请求会等待锁释放,通过双重检查直接复用已刷新的Token,避免重复发起刷新请求
方法二:Polly层面结合共享状态控制
若需在Polly策略中实现逻辑,可借助共享的刷新状态对象配合异步锁定,但本质和方法一逻辑一致,复杂度更高,不如直接在授权服务中处理清晰。
关于舱壁断路器的说明
舱壁断路器的核心作用是限制并发请求数量、防止服务过载,无法直接解决Token并发刷新的问题,因此不适合当前场景。
内容的提问来源于stack exchange,提问作者moly
相关产品推荐
相关产品推荐

