.NET Core HttpClient+Consul实现服务发现
简介
随着.NET Core的不断发展与成熟,基于.NET Core实现微服务的解决方案也越来越多。这其中必然需要注册中心,Consul成为了.NET Core实现服务注册与发现的首选。类似的解决方案还有很多比如Netflix Eureka,也有关于结合.NET Core的案例比如比较知名的就是SteeltoeOSS.Discovery 这里就不过多的介绍了,有兴趣的小伙伴可以自己在网上查阅资料。接下来我们讲解如何实现HttpClient结合Consul实现服务发现。

入门
关于Consul如何入门,相信很多小伙伴已经非常熟练了,这里就不再赘述。如果还有不熟悉的小伙伴请查阅Edison Zhou的 https://www.cnblogs.com/edisonchou/p/9124985.html 写的非常详细。
初步实现
如何将发现的地址结合到HttpClient相信很多小伙伴首先想到的就是如下方法
public string LookupService(string serviceName)
{
using (ConsulClient consulClient = new ConsulClient(config=>config.Address=new Uri("http://localhost:8500/")))
{
var services = _consulClient.Catalog.Service(serviceName).Result.Response;
if (services != null && services.Any())
{
//模拟负载均衡算法(随机获取一个地址)
int index = r.Next(services.Count());
var service = services.ElementAt(index);
return $"{service.ServiceAddress}:{service.ServicePort}";
}
return null;
}
}
然后调用的时候采用类似的方法
public async Task<Person> GetPerson(int personId)
{
using (HttpClient client = new HttpClient())
{
var response = await client.GetAsync($"http://{LookupService("PersonService")}/Person/GetPerson?personId={personId}");
var jsonResult = await response.Content.ReadAsStringAsync();
return jsonResult.FromJson<Person>();
}
}
或者封装了一层HttpHelper里面封装了针对Get Post的调用方法。

借助HttpMessageHandler
上面的方式实现确实是实现了,但是老是感觉差点意思,如果我在HttpHelper里多封装几个方法,那岂不是每个方法里面都得调用一次LookupService方法,总感觉不够优雅。难道没有更好的办法了吗? Of course,有的小伙伴可能发现了HttpClient构造函数有几个重载方法
/// <summary>Initializes a new instance of the <see cref="T:System.Net.Http.HttpClient" /> class using a <see cref="T:System.Net.Http.HttpClientHandler" /> that is disposed when this instance is disposed.</summary>
public HttpClient ()
: base (null);
/// <summary>Initializes a new instance of the <see cref="T:System.Net.Http.HttpClient" /> class with the specified handler. The handler is disposed when this instance is disposed.</summary>
/// <param name="handler">The HTTP handler stack to use for sending requests.</param>
/// <exception cref="T:System.ArgumentNullException">The <paramref name="handler" /> is <see langword="null" />.</exception>
public HttpClient (HttpMessageHandler handler)
: base (null); /// <summary>Initializes a new instance of the <see cref="T:System.Net.Http.HttpClient" /> class with the provided handler, and specifies whether that handler should be disposed when this instance is disposed.</summary>
/// <param name="handler">The <see cref="T:System.Net.Http.HttpMessageHandler" /> responsible for processing the HTTP response messages.</param>
/// <param name="disposeHandler">
/// <see langword="true" /> if the inner handler should be disposed of by HttpClient.Dispose; <see langword="false" /> if you intend to reuse the inner handler.</param>
/// <exception cref="T:System.ArgumentNullException">The <paramref name="handler" /> is <see langword="null" />.</exception>
public HttpClient (HttpMessageHandler handler, bool disposeHandler)
: base (null);
其中我们看到了HttpMessageHandler这是处理HttpClient消息的拦截器类,通过它可以获取和设置HttpClient的请求和返回内容。 具体实现如下
/// <summary>A base type for HTTP message handlers.</summary>
public abstract class HttpMessageHandler : IDisposable
{
/// <summary>Releases the unmanaged resources and disposes of the managed resources used by the <see cref="T:System.Net.Http.HttpMessageHandler" />.</summary>
public void Dispose (); /// <summary>Releases the unmanaged resources used by the <see cref="T:System.Net.Http.HttpMessageHandler" /> and optionally disposes of the managed resources.</summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources; <see langword="false" /> to releases only unmanaged resources.</param>
protected virtual void Dispose (bool disposing); /// <summary>Send an HTTP request as an asynchronous operation.</summary>
/// <param name="request">The HTTP request message to send.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>The task object representing the asynchronous operation.</returns>
/// <exception cref="T:System.ArgumentNullException">The <paramref name="request" /> was <see langword="null" />.</exception>
protected internal abstract Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken);
}
这个一个抽象类,通过继承这个抽象类,实现SendAsync抽象方法可以处理请求消息和返回消息。我们就从这里入手了,我们使用是DelegatingHandler这也是一个抽象类,这个抽象类继承自HttpMessageHandler是系统自带的一个抽象类,其实常用的还有一个叫HttpClientHandler,这个类也是继承自HttpMessageHandler,且提供了一些设置和获取操作输出和输出的具体实现。这里我们选用实现DelegatingHandler抽象类,至于为什么下篇文章会做详解,自定义一个ConsulDiscoveryDelegatingHandler实现类具体代码如下
public class ConsulDiscoveryDelegatingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var current = request.RequestUri;
try
{
//调用的服务地址里的域名(主机名)传入发现的服务名称即可
request.RequestUri = new Uri($"{current.Scheme}://{LookupService(current.Host)}/{current.PathAndQuery}");
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
catch (Exception e)
{
throw;
}
finally
{
request.RequestUri = current;
}
} private string LookupService(string serviceName)
{
using (ConsulClient consulClient = new ConsulClient(config=>config.Address=new Uri("http://localhost:8500/")))
{
var services = _consulClient.Catalog.Service(serviceName).Result.Response;
if (services != null && services.Any())
{
//模拟负载均衡算法(随机获取一个地址)
int index = r.Next(services.Count());
var service = services.ElementAt(index);
return $"{service.ServiceAddress}:{service.ServicePort}");
}
return null;
}
}
}
这样的话就大致实现了一个基于consul 发现的ConsulDiscoveryDelegatingHandler,具体的使用方式如下
public async Task<Person> GetPerson(int personId)
{
using (HttpClient client = new HttpClient(new ConsulDiscoveryDelegatingHandler()))
{
//调用时的域名(主机名)传入服务发现的名称即可
var response = await client.GetAsync($"http://PersonService/Person/GetPerson?personId={personId}");
var jsonResult = await response.Content.ReadAsStringAsync();
return jsonResult.FromJson<Person>();
}
}
到这里为止,关于HttpClient结合Consul实现服务发现的具体实现大致就差不多了,本身还存在很多不足,比如没有结合自带的IOC,没有做连接异常处理等等。但是能给大家提供一些思路或者帮助本人就已经满足了。

最后
到这里还并没有结束,相信很多小伙伴都知道HttpClient存在不足,就是Dispose的时候,套接字本身会延迟释放,会导致端口号占用的问题,高并发情况下会导致端口号用尽,导致服务器拒绝服务。虽然可以通过单例或者设置系统句柄释放时间解决这个问题,但是还是会存在一定的问题。庆幸的是微软也意识到了这个问题,从.NET Core 2.1版本开始推出了HttpClientFactory 通过池化技术管理HttpClient实例能很好的解决这个问题。在以后的版本里我们去访问网络请求都会使用HttpClientFactory。下一篇文章,我将会通过分析HttpClientFactory源码的方式,一步步探讨如何使用更优雅的方式实现HttpClientFactory+Consul实现服务发现。
本系列未完待续。。。
.NET Core HttpClient+Consul实现服务发现的更多相关文章
- .NET Core HttpClientFactory+Consul实现服务发现
前言 上篇文章.NET Core HttpClient+Consul实现服务发现提到过,HttpClient存在套接字延迟释放的问题,高并发情况导致端口号被耗尽引起服务器拒绝服务的问题.好在微软意识到 ...
- .net core grpc consul 实现服务注册 服务发现 负载均衡(二)
在上一篇 .net core grpc 实现通信(一) 中,我们实现的grpc通信在.net core中的可行性,但要在微服务中真正使用,还缺少 服务注册,服务发现及负载均衡等,本篇我们将在 .net ...
- .Net Core Grpc Consul 实现服务注册 服务发现 负载均衡
本文是基于..net core grpc consul 实现服务注册 服务发现 负载均衡(二)的,很多内容是直接复制过来的,..net core grpc consul 实现服务注册 服务发现 负载均 ...
- Api网关Kong集成Consul做服务发现及在Asp.Net Core中的使用
写在前面 Api网关我们之前是用 .netcore写的 Ocelot的,使用后并没有完全达到我们的预期,花了些时间了解后觉得kong可能是个更合适的选择. 简单说下kong对比ocelot打动我的 ...
- 使用Consul做服务发现的若干姿势
从2016年起就开始接触Consul,使用的主要目的就是做服务发现,后来逐步应用于生产环境,并总结了少许使用经验.最开始使用Consul的人不多,为了方便交流创建了一个QQ群,这两年微服务越来越火,使 ...
- Consul做服务发现
使用Consul做服务发现的若干姿势 https://www.cnblogs.com/bossma/p/9756809.html 从2016年起就开始接触Consul,使用的主要目的就是做服务发现,后 ...
- Go | Go 使用 consul 做服务发现
Go 使用 consul 做服务发现 目录 Go 使用 consul 做服务发现 前言 一.目标 二.使用步骤 1. 安装 consul 2. 服务注册 定义接口 具体实现 测试用例 3. 服务发现 ...
- go-micro使用Consul做服务发现的方法和原理
go-micro v4默认使用mdns做服务发现.不过也支持采用其它的服务发现中间件,因为多年来一直使用Consul做服务发现,为了方便和其它服务集成,所以还是选择了Consul.这篇文章将介绍go- ...
- ASP.NET CORE 使用Consul实现服务治理与健康检查(2)——源码篇
题外话 笔者有个习惯,就是在接触新的东西时,一定要先搞清楚新事物的基本概念和背景,对之有个相对全面的了解之后再开始进入实际的编码,这样做最主要的原因是尽量避免由于对新事物的认知误区导致更大的缺陷,Bu ...
随机推荐
- CYQ.Data 轻量数据层之路 使用篇-MProc 存储过程与SQL 视频[最后一集] H (二十八)
2019独角兽企业重金招聘Python工程师标准>>> 说明: 本次录制主要为使用篇:CYQ.Data 轻量数据层之路 使用篇五曲 MProc 存储过程与SQL(十六) 的附加视 ...
- 当setWidth()和setHeight()方法不起作用时
当在Android开发中用方法setWidth()和setHeight()动态设置控件的宽高时,当被改后的宽高小雨原来的宽高时,这两个方法将不会生效. 解决办法: 1 2 3 4 LayoutPara ...
- 1745 Divisibility
Divisibility Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 14084 Accepted: 4989 Descrip ...
- POJ - 2251 Dungeon Master (搜索)
You are trapped in a 3D dungeon and need to find the quickest way out! The dungeon is composed of un ...
- postman(全局变量设置)
全局变量 全局变量作用于整个postman工具及所有环境 1.点击小齿轮进入到变量添加页面,点击Globals添加全局变量 2.输入变量名称和变量值 3.接口中设置变量 4.调用 Globals 变量 ...
- DVWA-对Command Injection(命令注入)的简单演示与分析
前言 上一篇文章中,对命令注入进行了简单的分析,有兴趣的可以去看一看,文章地址 https://www.cnblogs.com/lxfweb/p/12828754.html,今天这篇文章以DVWA的C ...
- 基于KepServer实现与S7-1200PLC之间的通信
对于学习上位机开发,有一种通信方式是必须要了解的,那就是OPC是OLE for Process Control的简称,然而随着技术的不断发展,人们开始对它有了新的定义,比如Open Platform ...
- Spring官网阅读(十七)Spring中的数据校验
文章目录 Java中的数据校验 Bean Validation(JSR 380) 使用示例 Spring对Bean Validation的支持 Spring中的Validator 接口定义 UML类图 ...
- Numpy-np.random.normal()正态分布
X ~ :随机变量X的取值和其对应的概率值P(X = ) 满足正态分布(高斯函数) 很多随机现象可以用正态分布描述或者近似描述 某些概率分布可以用正态分布近似计算 正态分布(又称高斯分布)的概率密度函 ...
- 【Elasticsearch学习】文档搜索全过程
在ES执行分布式搜索时,分布式搜索操作需要分散到所有相关分片,若一个索引有3个主分片,每个主分片有一个副本分片,那么搜索请求会在这6个分片中随机选择3个分片,这3个分片有可能是主分片也可能是副本分片, ...