简介

  随着.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实现服务发现的更多相关文章

  1. .NET Core HttpClientFactory+Consul实现服务发现

    前言 上篇文章.NET Core HttpClient+Consul实现服务发现提到过,HttpClient存在套接字延迟释放的问题,高并发情况导致端口号被耗尽引起服务器拒绝服务的问题.好在微软意识到 ...

  2. .net core grpc consul 实现服务注册 服务发现 负载均衡(二)

    在上一篇 .net core grpc 实现通信(一) 中,我们实现的grpc通信在.net core中的可行性,但要在微服务中真正使用,还缺少 服务注册,服务发现及负载均衡等,本篇我们将在 .net ...

  3. .Net Core Grpc Consul 实现服务注册 服务发现 负载均衡

    本文是基于..net core grpc consul 实现服务注册 服务发现 负载均衡(二)的,很多内容是直接复制过来的,..net core grpc consul 实现服务注册 服务发现 负载均 ...

  4. Api网关Kong集成Consul做服务发现及在Asp.Net Core中的使用

    写在前面   Api网关我们之前是用 .netcore写的 Ocelot的,使用后并没有完全达到我们的预期,花了些时间了解后觉得kong可能是个更合适的选择. 简单说下kong对比ocelot打动我的 ...

  5. 使用Consul做服务发现的若干姿势

    从2016年起就开始接触Consul,使用的主要目的就是做服务发现,后来逐步应用于生产环境,并总结了少许使用经验.最开始使用Consul的人不多,为了方便交流创建了一个QQ群,这两年微服务越来越火,使 ...

  6. Consul做服务发现

    使用Consul做服务发现的若干姿势 https://www.cnblogs.com/bossma/p/9756809.html 从2016年起就开始接触Consul,使用的主要目的就是做服务发现,后 ...

  7. Go | Go 使用 consul 做服务发现

    Go 使用 consul 做服务发现 目录 Go 使用 consul 做服务发现 前言 一.目标 二.使用步骤 1. 安装 consul 2. 服务注册 定义接口 具体实现 测试用例 3. 服务发现 ...

  8. go-micro使用Consul做服务发现的方法和原理

    go-micro v4默认使用mdns做服务发现.不过也支持采用其它的服务发现中间件,因为多年来一直使用Consul做服务发现,为了方便和其它服务集成,所以还是选择了Consul.这篇文章将介绍go- ...

  9. ASP.NET CORE 使用Consul实现服务治理与健康检查(2)——源码篇

    题外话 笔者有个习惯,就是在接触新的东西时,一定要先搞清楚新事物的基本概念和背景,对之有个相对全面的了解之后再开始进入实际的编码,这样做最主要的原因是尽量避免由于对新事物的认知误区导致更大的缺陷,Bu ...

随机推荐

  1. vue2.x学习笔记(二十九)

    接着前面的内容:https://www.cnblogs.com/yanggb/p/12682822.html. 路由 官方路由 对于大多数的单页面应用,都推荐使用官方支持的vue-router库. 从 ...

  2. spark系列-2、Spark 核心数据结构:弹性分布式数据集 RDD

    一.RDD(弹性分布式数据集) RDD 是 Spark 最核心的数据结构,RDD(Resilient Distributed Dataset)全称为弹性分布式数据集,是 Spark 对数据的核心抽象, ...

  3. 《图解 HTTP》 摘要一

    学习过程对书本的内容的摘要以及总结,逐步完善,带有个人理解成分. Web 及网络基础 使用 HTTP 协议访问 Web 客户端:通过获取请求获取服务资源的 Web 浏览器等 HTTP 全称:Htype ...

  4. Ribbon 框架简介及搭建

    2019独角兽企业重金招聘Python工程师标准>>> Ribbon简介 1.  负载均衡框架,支持可插拔式的负载均衡规则 2.  支持多种协议,如HTTP.UDP等 3.  提供负 ...

  5. React Native中自定义导航条

    这是2017年年初开始的公司的项目,对于导航条的要求很高,Android和iOS上必须用一致的UI,按钮位置还有各种颜色都有要求,而且要适应各种奇葩要求. 尝试了一下当时React Native自带的 ...

  6. vue中 $refs的基本用法

    骚年,我看你骨骼惊奇,有撸代码的潜质,这里有324.57GB的前端学习资料传授于你!什么,你不信??? 先随便看几个图: 肯定没看够.再来个GIF图热个身??? 那么问题来了,如果你也想入坑前端或者学 ...

  7. ELK收集日志到mysql数据库

    场景需求 在使用ELK对日志进行收集的时候,如果需要对数据进行存档,可以考虑使用数据库的方式.为了便于查询,可以同时写一份数据到Elasticsearch 中. 环境准备 CentOS7系统: 192 ...

  8. FileZilla更新服务器文件后浏览器没有刷新的一种常见情况

    一.问题描述 在使用FileZilla更新服务器文件时,常出现的一种情况是: 刷新浏览器,发现该网页并未更新.但是检查网页源代码可以发现文件已经更新了,这就奇怪了,是服务器出了问题吗?还是FileZi ...

  9. Vim Operations

    Vim有三种模式:输入模式.命令模式和末行命令模式. 输入模式用来输入文字,命令模式用来下达编排文件的操作指令,末行命令模式用来进行文件存档.离开编辑器等操作. 进入及离开 末行模式下: :w 保存当 ...

  10. vue 跳转并传参,实现数据实时更新

    原文链接:点我 比如我现在在页面A跳转到页面B,A中的router-link :to={path:’B’,params:{id:’5’}} 求助:在页面B中的mounted生命周期函数中使用this. ...