写在前面

前面两篇文章透过源码角度,理解了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注册
   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)));

  • 声明Policy注册对象,并将超时策略对象添加进去
   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());

HttpClient熔断器模式的实现

如果非常了解Polly库的使用,那么熔断器模式的实现也会非常简单,

   1:  var policyRegistry = services.AddPolicyRegistry();
   2:   
   3:  policyRegistry.Add("MyCircuitBreaker",HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(handledEventsAllowedBeforeBreaking: ,durationOfBreak: TimeSpan.FromSeconds()));

这里的熔断器设置规则是在连续10次请求失败后,会暂停30秒。这个地方可以写个扩展方法注册到IServiceCollection中。

HttpClient日志记录与追踪链

日志记录这块与追踪链,我们一般会通过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/

.NET Core 3.0深入源码理解HttpClientFactory之实战的更多相关文章

  1. .NET Core 3.0之深入源码理解HttpClientFactory(二)

      写在前面 上一篇文章讨论了通过在ConfigureServices中调用services.AddHttpClient()方法,并基于此进一步探讨了DefaultHttpClientFactory是 ...

  2. .NET Core 3.0之深入源码理解HttpClientFactory(一)

    写在前面 创建HttpClient实例的时候,在内部会创建HttpMessageHandler链,我们知道HttpMessageHandler是负责建立连接的抽象处理程序,所以HttpClient的维 ...

  3. .NET Core 3.0之深入源码理解Startup的注册及运行

    原文:.NET Core 3.0之深入源码理解Startup的注册及运行   写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程 ...

  4. VUEJS2.0源码理解--优

    VUEJS2.0源码理解 http://jiongks.name/blog/vue-code-review/#pingback-112428

  5. 从路由原理出发,深入阅读理解react-router 4.0的源码

      react-router等前端路由的原理大致相同,可以实现无刷新的条件下切换显示不同的页面.路由的本质就是页面的URL发生改变时,页面的显示结果可以根据URL的变化而变化,但是页面不会刷新.通过前 ...

  6. 基于SpringBoot的Environment源码理解实现分散配置

    前提 org.springframework.core.env.Environment是当前应用运行环境的公开接口,主要包括应用程序运行环境的两个关键方面:配置文件(profiles)和属性.Envi ...

  7. jQuery 2.0.3 源码分析Sizzle引擎解析原理

    jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理 声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 先来回答博友的提问: 如何解析 div > p + ...

  8. Caffe源码理解2:SyncedMemory CPU和GPU间的数据同步

    目录 写在前面 成员变量的含义及作用 构造与析构 内存同步管理 参考 博客:blog.shinelee.me | 博客园 | CSDN 写在前面 在Caffe源码理解1中介绍了Blob类,其中的数据成 ...

  9. springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂

    前言 开心一刻 女儿: “妈妈,你这么漂亮,当年怎么嫁给了爸爸呢?” 妈妈: “当年你爸不是穷嘛!‘ 女儿: “穷你还嫁给他!” 妈妈: “那时候刚刚毕业参加工作,领导对我说,他是我的扶贫对象,我年轻 ...

随机推荐

  1. (数据科学学习手札62)详解seaborn中的kdeplot、rugplot、distplot与jointplot

    一.简介 seaborn是Python中基于matplotlib的具有更多可视化功能和更优美绘图风格的绘图模块,当我们想要探索单个或一对数据分布上的特征时,可以使用到seaborn中内置的若干函数对数 ...

  2. python函数之enumerate()

    enumrate 语法: # enumerate(sequence, [start=]) 参数:# sequence -- 一个序列.迭代器或其他支持迭代对象.# start -- 下标起始位置. 使 ...

  3. 记一次linux通过jstack定位CPU使用过高问题或排查线上死锁问题

    一.java定位进程 在服务器中终端输入命令:top 可以看到进程ID,为5421的cpu这列100多了. 记下这个数字:5421 二.定位问题进程对应的线程 然后在服务器中终端输入命令:top -H ...

  4. 50行Python代码,教你获取公众号全部文章

    > 本文首发自公众号:python3xxx 爬取公众号的方式常见的有两种 - 通过搜狗搜索去获取,缺点是只能获取最新的十条推送文章 - 通过微信公众号的素材管理,获取公众号文章.缺点是需要申请自 ...

  5. Vs连接Mysql数据库

    Vs连接Mysql数据库步骤 1. 首先下载mysql数据库,安装,建库建表 https://www.yiibai.com/mysql/getting-started-with-mysql-store ...

  6. [Python学习]错误篇一

    REFERENCE:<Head First Python> ID:我的第一篇[Python学习] BIRTHDAY:2019.7.6 EXPERIENCE_SHARING:两个程序错误类型 ...

  7. springboot不同环境打包

    1. 场景描述 springboot+maven打包,项目中经常用到不同的环境下打包不同的配置文件,比如连接的数据库.配置文件.日志文件级别等都不一样. 2. 解决方案 在pom.xml文件中定义 2 ...

  8. 20140117-配置文件为什么放在UI层

    配置文件为什么放在UI层 (刚才写着代码突然忘了配置文件为什么要放在UI层了,只记得晓虎老师强调过.找了半天视频……) 现总结一下: 晓虎老师给出的理由,大体如下:比如一个web项目,分成三层,DAL ...

  9. java学习笔记(基础篇)—变量与表达式

    一:局部变量和实例变量 定义变量是指设定变量的数据类型和变量的名字,Java语言要求变量遵循先定义,再初始化,然后使用的规则. 作用域:指它的存在范围,只有在这个范围内,程序代码才能访问它. 变量的生 ...

  10. 【题解】搬书-C++

    搬书 Description 陈老师桌上的书有三堆,每一堆都有厚厚的一叠,你想逗一下陈老师,于是你设计一个最累的方式给他,让他把书 拿下来给同学们.若告诉你这三堆分别有i,j,k本书,以及每堆从下到上 ...