前言

关于HttpClient的使用,个人在很多场景都派上用场了,比如在Winform或后台服务中用其调用接口获取和上传数据、微服务中用其进行各服务之间的数据共享等,到目前来看,似乎还没有出现过什么问题,但当我看到官方文档介绍使用方式时,再回顾之前项目的代码,只能说没出问题比较庆幸。

官方文档介绍的大概意思如下:

HttpClient类使用比较简单,但在某些情况下,许多开发人员却并未正确使用该类;虽然此类实现 IDisposable,但在 using 语句中声明和实例化它并非首选操作,因为释放 HttpClient 对象时,基础套接字不会立即释放,这可能会导致套接字耗尽问题,最终可能会导致 SocketException 错误。要解决此问题,推荐的方法是将 HttpClient 对象创建为单一对象或静态对象

看到这,有一点点小不安(因为有些项目就是用using的方式),虽然目前的并发量还不至于导致SocketException异常的发生,但优化得马不停蹄的安排上;先来探探HttpClient,再来说说IHttpClientFactory。

正文

1. HttpClient好像一直没用对

这里创建了一个控制台程序进行测试(.NetCore3.1),代码比较简单,如下:

注:代码中访问的地址是用Nginx搭建在阿里云上搭建的站点,没有做负载,所以看Socket状态比较直观。

上面代码执行完成之后就退出了,理论情况来说,上面程序执行完毕之后,就不应该占用资源啦,但通过netstat查看,的确还有Socket被占用,测试如下:

  • Windows 测试

    执行如下命令看结果:

    netstat -ano | findstr TIME_WAIT

  • Linux测试

    我的云服务器之前就把.NetCore运行环境安装好了,所以创建一个目录,将编译之后的文件通过Xftp将文件传到云服务器,执行以下命令即可:

    # 注意,这里指定启动的是dll文件
    dotnet HttpClientConsoleDemo.dll

    查看端口情况,执行以下命令即可:

    netstat -ant | grep TIME_WAIT # 查看端口占用情况,找到状态为TIME_WAIT

    TIME_WAIT 是主动关闭 TCP 连接的那一方出现的状态,系统会在 TIME_WAIT 状态下等待 2MSL(maximum segment lifetime )后才能释放连接(端口),目的是为了在TCP 四次挥手关闭连接机制中,保证 ACK 重发和丢弃延迟数据。按照解释来看,这种做法也算是合情合理,保证数据传输嘛,但的确就是占用资源啦;具体关于网络的相关知识,小伙伴们再去查阅一下。

经过Windows和Linux的测试,大概差不多两分钟的时间Socket才完全被释放,可想,如果是在高并发情况下,每台机器的能开的连接数是有限的,使用这种方式进行服务和服务之间交互数据,那肯定会出现问题。

而从理论上来讲,只要HttpClient继承了IDisposable接口,using块执行完就可以释放掉资源,而HttpClient确实是间接继承了IDisposable(直接继承HttpMessageInvoker ,而HttpMessageInvoker继承了IDisposable接口)。但在这里,使用HttpClient分配的Socket端口没有得到及时释放,为避免这个问题,官方推荐用静态变量的方式使用HttpClient。

2. HttpClient换种方式好多啦

按照官方建议,将HttpClient变量定义为静态变量,代码如下:

运行程序,执行netstat命令可以看到,资源占用情况明显减少了。

如果使用静态变量,看似解决了对应的问题,但若想针对不同的请求设置不同的头信息时就显得不太方便,至于其他问题我暂时还没遇见过(官方说这种方式不支持 DNS 变更)。先忽略其他问题,在.NetCore2.1开始出现了IHttpClientFactory ,据说是可以解决之前HttpClient面临的一些问题,所以有需要用HttpClient的场景,直接用IHttpClientFactory 就妥啦,不信就来试试。

3. IHttpClientFactory用起来很给力

IHttpClientFactory是在.NETCore 2.1 开始提供的,默认实现为 DefaultHttpClientFactory ,专门用于创建在应用程序中用到的 HttpClient实例,自动维护内部的HttpMessageHandler池及其生命周期。主要功能如下:

  • 支持命名化、类型化配置,集中管理配置,避免冲突;
  • 出站请求管道配置灵活,轻松实现对请求生命周期的管理;
  • 合理管理内部HttpMessageHanlder的生命周期,避免资源占用和DNS刷新等问题
  • 内置日志记录器

接下来就来看看IHttpClientFactory到底有多给力;

3.1 控制台演示

由于内部需要使用了一些服务,并且是采用DI的形式注入的,所以首先要把依赖注入的相关的包引入进来,代码如下图:

先来简单看一下需要注册的服务,后续会好好说:

运行程序,然后通过以下命令查看端口占用情况;

netstat -ano | findstr TIME_WAIT    # 根据状态去找
netstat -ano | findstr 47.113.204.41 # 根据IP去找

最终没有见到很明显的资源占用情况,有没有给力一点;还有一些比较常用的方式,在WebApi项目中一一演示(毕竟微服务中服务间通信还是比较常用的)。

3.2 WebApi项目演示
  • 创建一个API项目,注册上相关服务即可

  • 增加一个测试控制器和测试接口

  • 运行看结果

    在API项目中是不是使用很简单,因为项目本身就内置了依赖注入相关功能,直接注册上服务就可以使用;但这还体现不出有多给力,接下来继续看看其他扩展方式的使用。

3.3 使用命名和类型模式区分不同HttpClient

使用步骤与3.2是一样的,只是在注册服务的时候不太一样而已。

  • 命名模式

    注册服务时代码如下:

    增加一个测试接口,运行结果和上面一样,只是HttpClient实例带的头信息不一样啦,如下:

  • 类型模式

    其实类型模式原理和命名模式是一样的,只是通过指定的类型名称作为对应HttpClient的名称,减少了单独定义名称的步骤,所以显得比较方便,个人比较喜欢这样用。

    首先定义一个业务处理类,直接使用HttpClient,代码如下:

    在Startup中注册服务时代码如下:

    增加一个测试接口,运行结果和上面一样,调用过程如下:

    进入TypeHttpClientService对应的方法,看到在注册时设置的头信息已经生效了,如下:

    执行完毕后,正常返回结果。类型模式不用单独为HttpClient起名,而是直接用指定类型的类名作为默认名,这样就相对方便啦;摘取获取类型名的源码如下:

    小扩展:自定义的管道类没有在Startup中手动注册,为什么能直接注入使用?

    答案:在services.AddHttpClient注册时,内部已经将对应Type注册好了,摘取代码如下:

    除了能轻松区分不同HttpClient之外,如果需要在请求过程中加入其它公共业务处理,可以通过增加自定义管道逻辑轻松实现。

3.4 在请求管道中增加自定义管道逻辑
  • 先定义一个管道类(CustomDelegatingHandler),继承DelegatingHandler,然后重写SendAsync方法

  • 在Startup中注册对应的服务,根据需要在HttpClient上加上自定义管道

  • 运行看效果,只有用到类型模式的HttpClient才会加入自定义管道,因为在配置的时候进行了针对性的配置

    这种方式是不是感觉和.NetCore的中间件管道很类似,编写自定义管道其实就类似于在编写一个中间件(至于原理,后续单独扒扒源码),轻松实现在请求前和响应后做相关业务处理,就像上面加的RequestID,对分析分布式事务及微服务调用链跟踪都有很大的帮助,所以小伙伴可以根据自己的需要封装自己想要的HttpClient。

4. IHttpClientFactory 搭配Polly就完美了

只要牵涉到网络,要想服务百分百稳定那是相当难,比如常遇到的网络不通、网络波动、远端服务挂了、远端服务响应慢等问题,都可能会影响用户对系统的体验,别慌,还记得之前说过专门为故障、弹性应变设计的Polly库吗(详情请看Polly-故障处理和弹性应对很有一手),如果HttpClientFactory能和Polly搭配使用岂不美哉;

其实是我们也能结合HttpClientFactory和Polly自己封装, 不过微软已经把轮子造好了,拿来用即可,如下步骤:

4.1 引入对应版本的包

引入Microsoft.Extensions.Http.Polly包,这里使用的是.NetCore3.1,所以引入对应的版本为3.X都行。

4.2 Startup中注册服务的时候指定策略

4.3 新增测试接口

4.4 运行看效果

在浏览器访问接口的时候,对比控制台打印的消息,会按照设定的时间间隔进行重试,效果很明显的。

对于IHttpClientFactory集成Polly的使用就先说到这,关于其他策略的演示,和单独使用Polly是一样的,相信小伙伴参照上面案例演示一定能搞定。对于Polly的其他策略使用,可以参考《Polly-故障处理和弹性应对很有一手》这篇文章,说的挺详细。

总结

IHttpClientFactory的应用暂时就先说到这,上面提到的功能只是平时自己常用的,其他功能小伙伴可以去探究探究;后续单独和小伙伴们一起扒扒源代码,整理整理一下流程。

如果现有项目需要用到HttpClient,建议通过IHttpClientFactory的方式,用起来方便、灵活,主要是是能避免一些问题。

博文源代码github地址:https://github.com/zyq025/DotNetCoreStudyDemo

一个被程序搞丑的帅小伙,关注"Code综艺圈",识别关注跟我一起学~~~

把HttpClient换成IHttpClientFactory之后,放心多了的更多相关文章

  1. 将 Photoshop CC 2015.5 英文界面换成中文, 英文与中文界面互换

    注:转载或引用请注明出处 在英文的win server 2012 r2 上安装PS CC 2015.5 时,安装程序自动按成了英文版的PS,那么如何将英文换成中文呢? 网上大多讲的都是将中文换成英文, ...

  2. 自己写的一个用js把select换成div与span与ul的东西

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  3. PHP的mod_rewrite重写模块将.php后缀换成.html

    apache Rewrite mod_rewrite的魔力 简单举例:创建三个文件.分别命名为 test.html,test.php和.htaccess test.html 输入: <h1> ...

  4. Win8换成Win7系统问题小结(修改主板BIOS方法)

    问题描述: 笔记本电脑W8系统使用不习惯,想要换成W7系统,但不管是用光盘安装亦或是用U盘安装,在设置系统启动项的时候,选择从光盘启动或从U盘启动,但是回车点了之后没反应. 下面就说说问题的原因及解决 ...

  5. php 把数字1-1亿换成汉字表述,例如 150 转成 一百五十

    /* 额,重新修改了下.现在估计没什么问题了.... */ 直接上实例 写到 千亿上了. /** * @author ja颂 * 把数字1-1亿换成汉字表述,如:123->一百二十三 * @pa ...

  6. 【代码笔记】iOS-把<br!>换成\n

    代码: - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. // ...

  7. iOS: 上传App到AppStore,由于Xcode上传太慢,换成Application Loader上传,速度秒传

    一.遇到的遭遇 在之前的项目开发中,本人有点固执,一直采用xcode打包后再上传,结果可想而知: (1)要么上传时速度慢的跟蜗牛似的,等的我心力交瘁(不排除网络不给力的原因,公司这个吊问题快把我气疯了 ...

  8. svn的差异查看器和合并工具换成BCompare.exe

    svn的差异查看器和合并工具换成BCompare.exe

  9. 当他们也换成了Linux OS

    近期,斯诺登的事闹得沸沸扬扬,美帝损失了公信.又有传言说Win8给美帝安全局留了后门?XP依旧是生命力旺盛. 还不就是因为那点事儿,看不到人家的源代码? 斗胆提一个问题,如果公务员们或者是一部分,开始 ...

随机推荐

  1. 截取字符串长度,超出部分用省略号代替 PHP

    function subText($text, $length){ if (mb_strlen($text, 'utf8') > $length) { return mb_substr($tex ...

  2. Mac安装python 环境& pychaem

    一.文档说明 在Mac上其实自带python环境,但是很多的library安装python是2.7的版本. 验证:可以在终端Terminal中输入:python 如下图是未安装之前,但是咱们需要在自己 ...

  3. Python数模笔记-Sklearn(5)支持向量机

    支持向量机(Support vector machine, SVM)是一种二分类模型,是按有监督学习方式对数据进行二元分类的广义线性分类器. 支持向量机经常应用于模式识别问题,如人像识别.文本分类.手 ...

  4. 关于MySQL参数,这些你要知道

    前言: 在前面一些文章中,经常能看到介绍某某参数的作用,可能有些小伙伴仍搞不清楚 MySQL 参数是啥.本篇文章我们来聊聊 MySQL 参数,学习下如何管理维护 MySQL 参数. 1.MySQL参数 ...

  5. python爬虫——《英雄联盟》英雄及皮肤图片

    还记得那些年一起网吧开黑通宵的日子吗?<英雄联盟>绝对是大学时期的风靡游戏,即使毕业多年的大学同学相聚,难免不怀念一番当时一起玩<英雄联盟>的日子. 今天就给大家分享一下英雄及 ...

  6. Gradle的环境安装与配置

    本文是基于Windows环境对Gradle-6.6版本进行的安装配置. 简介 下载 安装 环境变量配置 开发工具配置 1.简介 Gradle是一个基于JVM的构建工具,是一款通用灵活的构建工具,支持m ...

  7. Flume 常用配置项

    注:以下配置项均为常见配置项,查询详细配置项可以访问 flume 官网 Source 常见配置项 Avro Source 配置项名称 默认值 描述 Channel – type – 组件类型名称,必须 ...

  8. mate-notification-daemon stopping, Mate notifications timeout

    mate-notification-daemon stopping, Mate notifications timeout Ask Question Asked 9 days ago Viewed 1 ...

  9. C++知识点案例 笔记-4

    1.纯虚函数 2.抽象类 3.内部类 4.运算符重载 5.类的函数重载 6.友元的函数重载 1.纯虚函数 ==纯虚函数== //有时基类中无法给出函数的具体体现,定义纯虚函数可以为派生函数保留一个函数 ...

  10. mysql基础之日志管理(查询日志、慢查询日志、错误日志、二进制日志、中继日志、事务日志)

    日志文件记录了MySQL数据库的各种类型的活动,MySQL数据库中常见的日志文件有 查询日志,慢查询日志,错误日志,二进制日志,中继日志 ,事务日志. 修改配置或者想要使配置永久生效需将内容写入配置文 ...