写在前面
前面两篇文章透过源码角度, 理解了 HttpClientFactory 的内部实现, 当我们在项目中使用时, 总会涉及以下几个问题:
HttpClient 超时处理以及重试机制
HttpClient 熔断器模式的实现
HttpClient 日志记录与追踪链
接下来我们将从使用角度对上述问题作出说明.
详细介绍
以下代码参考了 MSDN, 因为代码里展示的 GitHub 接口确实可以调通, 省的我再写一个接口出来测试了.
HttpClient 超时处理和重试机制
在此之前, 我们需要了解一下 Polly 这个库, Polly 是一款基于. NET 的弹性及瞬间错误处理库, 它允许开发人员以顺畅及线程安全的方式执行重试 (Retry), 断路器(Circuit), 超时(Timeout), 隔板隔离(Bulkhead Isolation) 及后背策略(Fallback).
以下代码描述了在. NET Core 3.0 中如何使用超时机制.
1: Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10))
那么如何将其注册到对应的 HttpClient 实例呢, 有很多种方式:
通过 AddPolicyHandler 注册
- services.AddHttpClient("github", c =>
- {
- c.BaseAddress = new Uri("https://api.github.com/");
- c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
- c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
- }).AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10)));
声明 Policy 注册对象, 并将超时策略对象添加进去
- var registry = services.AddPolicyRegistry();
- var timeout = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10));
- registry.Add("regular", timeout);
调用方式
- services.AddHttpClient("github", c =>
- {
- c.BaseAddress = new Uri("https://api.github.com/");
- c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); // GitHub API versioning
- c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); // GitHub requires a user-agent
- }).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 重试.
- private class RetryHandler : DelegatingHandler
- {
- public int RetryCount { get; set; } = 5;
- protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
- {
- for (var i = 0; i <RetryCount; i++)
- {
- try
- {
- return await base.SendAsync(request, cancellationToken);
- }
- catch (HttpRequestException) when (i == RetryCount - 1)
- {
- throw;
- }
- catch (HttpRequestException)
- {
- // 五十毫秒后重试
- await Task.Delay(TimeSpan.FromMilliseconds(50));
- }
- } 23: } 24: }
注册方式如下:
- services.AddHttpClient("github", c =>
- {
- c.BaseAddress = new Uri("https://api.github.com/");
- c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); // GitHub API versioning
- c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); // GitHub requires a user-agent
- })
- .AddHttpMessageHandler(() => new RetryHandler());
HttpClient 熔断器模式的实现
如果非常了解 Polly 库的使用, 那么熔断器模式的实现也会非常简单,
- var policyRegistry = services.AddPolicyRegistry();
- policyRegistry.Add("MyCircuitBreaker",HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerasync(handledEventsAllowedBeforeBreaking: 10,durationOfBreak: TimeSpan.FromSeconds(30)));
这里的熔断器设置规则是在连续 10 次请求失败后, 会暂停 30 秒. 这个地方可以写个扩展方法注册到 IServiceCollection 中.
HttpClient 日志记录与追踪链
日志记录这块与追踪链, 我们一般会通过 request.Header 实现, 而在微服务中, 十分关注相关调用方的信息及其获取, 一般的做法是通过增加请求 Id 的方式来确定请求及其相关日志信息.
实现思路是增加一个 DelegatingHandler 实例, 用以记录相关的日志以及请求链路
- public class TraceEntryHandler : DelegatingHandler
- {
- private TraceEntry TraceEntry { get; set; }
- public TraceEntryHandler(TraceEntry traceEntry)
- {
- this.TraceEntry = traceEntry;
- } 9:
- protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
- {
- request.Headers.TryAddWithoutValidation("X-TRACE-CID", this.TraceEntry.ClientId);
- request.Headers.TryAddWithoutValidation("X-TRACE-RID", this.TraceEntry.RequestId);
- request.Headers.TryAddWithoutValidation("X-TRACE-SID", this.TraceEntry.SessionId);
- if (this.TraceEntry.SourceIP.IsNullOrEmpty())
- {
- request.Headers.TryAddWithoutValidation("X-TRACE-IP", this.TraceEntry.SourceIP);
- }
- if (this.TraceEntry.SourceUserAgent.IsNullOrEmpty())
- {
- request.Headers.TryAddWithoutValidation("X-TRACE-UA", this.TraceEntry.SourceUserAgent);
- }
- if (this.TraceEntry.UserId.IsNullOrEmpty())
- {
- request.Headers.TryAddWithoutValidation("X-TRACE-UID", this.TraceEntry.UserId);
- }
- return base.SendAsync(request, cancellationToken);
- } 32: }
我在查找相关资料的时候, 发现有个老外使用 CorrelationId 组件实现, 作为一种实现方式, 我决定要展示一下, 供大家选择:
- public class CorrelationIdDelegatingHandler : DelegatingHandler
- {
- private readonly ICorrelationContextAccessor correlationContextAccessor;
- private readonly IOptions<CorrelationIdOptions> options;
- public CorrelationIdDelegatingHandler(
- ICorrelationContextAccessor correlationContextAccessor,
- IOptions<CorrelationIdOptions> options)
- {
- this.correlationContextAccessor = correlationContextAccessor;
- this.options = options;
- } 13:
- protected override Task<HttpResponseMessage> SendAsync(
- HttpRequestMessage request,
- CancellationToken cancellationToken)
- {
- if (!request.Headers.Contains(this.options.Value.Header))
- {
- request.Headers.Add(this.options.Value.Header, correlationContextAccessor.CorrelationContext.CorrelationId);
- } 22:
- // Else the header has already been added due to a retry.
- return base.SendAsync(request, cancellationToken);
- } 27: }
参考链接:
来源: https://www.cnblogs.com/edison0621/p/11298882.html