.NET Core HttpClient+Consul实现服务发现
发布日期:2021-05-09 08:01:34 浏览次数:9 分类:博客文章

本文共 5883 字,大约阅读时间需要 19 分钟。

简介

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

 

入门

   关于Consul如何入门,相信很多小伙伴已经非常熟练了,这里就不再赘述。如果还有不熟悉的小伙伴请查阅Edison Zhou的  写的非常详细。

初步实现

      如何将发现的地址结合到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
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
(); }}

或者封装了一层HttpHelper里面封装了针对Get Post的调用方法。

借助HttpMessageHandler

  上面的方式实现确实是实现了,但是老是感觉差点意思,如果我在HttpHelper里多封装几个方法,那岂不是每个方法里面都得调用一次LookupService方法,总感觉不够优雅。难道没有更好的办法了吗? Of course,有的小伙伴可能发现了HttpClient构造函数有几个重载方法

/// Initializes a new instance of the 
class using a
that is disposed when this instance is disposed.
public HttpClient () : base (null); /// Initializes a new instance of the
class with the specified handler. The handler is disposed when this instance is disposed.
/// The HTTP handler stack to use for sending requests.///
The
is
.
public HttpClient (HttpMessageHandler handler) : base (null);/// Initializes a new instance of the
class with the provided handler, and specifies whether that handler should be disposed when this instance is disposed.
/// The
responsible for processing the HTTP response messages./// ///
if the inner handler should be disposed of by HttpClient.Dispose;
if you intend to reuse the inner handler.///
The
is
.
public HttpClient (HttpMessageHandler handler, bool disposeHandler) : base (null);

其中我们看到了HttpMessageHandler这是处理HttpClient消息的拦截器类,通过它可以获取和设置HttpClient的请求和返回内容。 具体实现如下

/// A base type for HTTP message handlers.public abstract class HttpMessageHandler : IDisposable{    /// Releases the unmanaged resources and disposes of the managed resources used by the 
.
public void Dispose (); /// Releases the unmanaged resources used by the
and optionally disposes of the managed resources.
/// ///
to release both managed and unmanaged resources;
to releases only unmanaged resources. protected virtual void Dispose (bool disposing); /// Send an HTTP request as an asynchronous operation. /// The HTTP request message to send. /// The cancellation token to cancel operation. ///
The task object representing the asynchronous operation.
///
The
was
.
protected internal abstract Task
SendAsync (HttpRequestMessage request, CancellationToken cancellationToken);}

  这个一个抽象类,通过继承这个抽象类,实现SendAsync抽象方法可以处理请求消息和返回消息。我们就从这里入手了,我们使用是DelegatingHandler这也是一个抽象类,这个抽象类继承自HttpMessageHandler是系统自带的一个抽象类,其实常用的还有一个叫HttpClientHandler,这个类也是继承自HttpMessageHandler,且提供了一些设置和获取操作输出和输出的具体实现。这里我们选用实现DelegatingHandler抽象类,至于为什么下篇文章会做详解,自定义一个ConsulDiscoveryDelegatingHandler实现类具体代码如下

public class ConsulDiscoveryDelegatingHandler : DelegatingHandler{     protected override async Task
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
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
(); }}

      到这里为止,关于HttpClient结合Consul实现服务发现的具体实现大致就差不多了,本身还存在很多不足,比如没有结合自带的IOC,没有做连接异常处理等等。但是能给大家提供一些思路或者帮助本人就已经满足了。

 

最后

      到这里还并没有结束,相信很多小伙伴都知道HttpClient存在不足,就是Dispose的时候,套接字本身会延迟释放,会导致端口号占用的问题,高并发情况下会导致端口号用尽,导致服务器拒绝服务。虽然可以通过单例或者设置系统句柄释放时间解决这个问题,但是还是会存在一定的问题。庆幸的是微软也意识到了这个问题,从.NET Core 2.1版本开始推出了 通过池化技术管理HttpClient实例能很好的解决这个问题。在以后的版本里我们去访问网络请求都会使用HttpClientFactory。下一篇文章,我将会通过分析HttpClientFactory源码的方式,一步步探讨如何使用更优雅的方式实现。

本系列未完待续。。。

 

上一篇:.NET Core HttpClientFactory+Consul实现服务发现
下一篇:5分钟学Go 基础01:初识 Go

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2025年04月04日 00时48分02秒