
前面两篇文章透过源码角度,理解了HttpClientFactory的内部实现,当我们在项目中使用时,总会涉及以下几个问题:
接下来我们将从使用角度对上述问题作出说明。
以下代码参考了MSDN,因为代码里展示的GitHub接口确实可以调通,省的我再写一个接口出来测试了。
在此之前,我们需要了解一下Polly这个库,Polly是一款基于.NET的弹性及瞬间错误处理库, 它允许开发人员以顺畅及线程安全的方式执行重试(Retry),断路器(Circuit),超时(Timeout),隔板隔离(Bulkhead Isolation)及后背策略(Fallback)。
以下代码描述了在.NET Core 3.0中如何使用超时机制。
1: Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10))那么如何将其注册到对应的HttpClient实例呢,有很多种方式:
1: services.AddHttpClient("github", c => 2: { 3: c.BaseAddress = new Uri("https://api.github.com/"); 4: 5: c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); 6: c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); 7: }).AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10))); 1: var registry = services.AddPolicyRegistry(); 2: var timeout = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10)); 3: registry.Add("regular", timeout);调用方式
1: services.AddHttpClient("github", c => 2: { 3: c.BaseAddress = new Uri("https://api.github.com/"); 4: 5: c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); // GitHub API versioning 6: c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); // GitHub requires a user-agent 7: }).AddPolicyHandlerFromRegistry("regular")Polly重试也很简单
1: var policyRegistry = services.AddPolicyRegistry(); 2: 3: policyRegistry.Add("MyHttpRetry",HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(3,retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));这里的重试设置是在第一次调用失败后,还会有三次机会继续重试,每个请求的时间间隔是指数级延迟。
重试功能除了可以使用Polly实现外,还可以使用DelegatingHandler,DelegatingHandler继承自HttpMessageHandler,用于”处理请求、响应回复“,本质上就是一组HttpMessageHandler的有序组合,可以视为是一个“双向管道”。 此处主要展示DelegatingHandler的使用方式,在实际使用中,仍然建议使用Polly重试。
1: private class RetryHandler : DelegatingHandler 2: { 3: public int RetryCount { get; set; } = 5; 4: 5: protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 6: { 7: for (var i = 0; i < RetryCount; i++) 8: { 9: try 10: { 11: return await base.SendAsync(request, cancellationToken); 12: } 13: catch (HttpRequestException) when (i == RetryCount - 1) 14: { 15: throw; 16: } 17: catch (HttpRequestException) 18: { 19: // 五十毫秒后重试 20: await Task.Delay(TimeSpan.FromMilliseconds(50)); 21: } 22: } 23: } 24: }注册方式如下:
1: services.AddHttpClient("github", c => 2: { 3: c.BaseAddress = new Uri("https://api.github.com/"); 4: 5: c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); // GitHub API versioning 6: c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); // GitHub requires a user-agent 7: }) 8: .AddHttpMessageHandler(() => new RetryHandler());如果非常了解Polly库的使用,那么熔断器模式的实现也会非常简单,
1: var policyRegistry = services.AddPolicyRegistry(); 2: 3: policyRegistry.Add("MyCircuitBreaker",HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(handledEventsAllowedBeforeBreaking: 10,durationOfBreak: TimeSpan.FromSeconds(30)));这里的熔断器设置规则是在连续10次请求失败后,会暂停30秒。这个地方可以写个扩展方法注册到IServiceCollection中。
日志记录这块与追踪链,我们一般会通过request.Header实现,而在微服务中,十分关注相关调用方的信息及其获取,一般的做法是通过增加请求Id的方式来确定请求及其相关日志信息。
实现思路是增加一个DelegatingHandler实例,用以记录相关的日志以及请求链路
1: public class TraceEntryHandler : DelegatingHandler 2: { 3: private TraceEntry TraceEntry { get; set; } 4: 5: public TraceEntryHandler(TraceEntry traceEntry) 6: { 7: this.TraceEntry = traceEntry; 8: } 9: 10: protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 11: { 12: request.Headers.TryAddWithoutValidation("X-TRACE-CID", this.TraceEntry.ClientId); 13: request.Headers.TryAddWithoutValidation("X-TRACE-RID", this.TraceEntry.RequestId); 14: request.Headers.TryAddWithoutValidation("X-TRACE-SID", this.TraceEntry.SessionId); 15: if (this.TraceEntry.SourceIP.IsNullOrEmpty()) 16: { 17: request.Headers.TryAddWithoutValidation("X-TRACE-IP", this.TraceEntry.SourceIP); 18: } 19: 20: if (this.TraceEntry.SourceUserAgent.IsNullOrEmpty()) 21: { 22: request.Headers.TryAddWithoutValidation("X-TRACE-UA", this.TraceEntry.SourceUserAgent); 23: } 24: 25: if (this.TraceEntry.UserId.IsNullOrEmpty()) 26: { 27: request.Headers.TryAddWithoutValidation("X-TRACE-UID", this.TraceEntry.UserId); 28: } 29: 30: return base.SendAsync(request, cancellationToken); 31: } 32: }我在查找相关资料的时候,发现有个老外使用CorrelationId组件实现,作为一种实现方式,我决定要展示一下,供大家选择:
1: public class CorrelationIdDelegatingHandler : DelegatingHandler 2: { 3: private readonly ICorrelationContextAccessor correlationContextAccessor; 4: private readonly IOptions<CorrelationIdOptions> options; 5: 6: public CorrelationIdDelegatingHandler( 7: ICorrelationContextAccessor correlationContextAccessor, 8: IOptions<CorrelationIdOptions> options) 9: { 10: this.correlationContextAccessor = correlationContextAccessor; 11: this.options = options; 12: } 13: 14: protected override Task<HttpResponseMessage> SendAsync( 15: HttpRequestMessage request, 16: CancellationToken cancellationToken) 17: { 18: if (!request.Headers.Contains(this.options.Value.Header)) 19: { 20: request.Headers.Add(this.options.Value.Header, correlationContextAccessor.CorrelationContext.CorrelationId); 21: } 22: 23: // Else the header has already been added due to a retry. 24: 25: return base.SendAsync(request, cancellationToken); 26: } 27: }参考链接:
https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.0
https://rehansaeed.com/optimally-configuring-asp-net-core-httpclientfactory/