.NET Core HttpClient+Consul实现服务发现

简介

  随着.NET Core的不断发展与成熟,基于.NET Core实现微服务的解决方案也愈来愈多。这其中必然须要注册中心,Consul成为了.NET Core实现服务注册与发现的首选。相似的解决方案还有不少好比Netflix Eureka,也有关于结合.NET Core的案例好比比较知名的就是SteeltoeOSS.Discovery 这里就不过多的介绍了,有兴趣的小伙伴能够本身在网上查阅资料。接下来咱们讲解如何实现HttpClient结合Consul实现服务发现。html

 

入门

   关于Consul如何入门,相信不少小伙伴已经很是熟练了,这里就再也不赘述。若是还有不熟悉的小伙伴请查阅Edison Zhou的 http://www.javashuo.com/article/p-wjrvtaag-ex.html 写的很是详细。git

初步实现

      如何将发现的地址结合到HttpClient相信不少小伙伴首先想到的就是以下方法github

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的调用方法。spring

借助HttpMessageHandler

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

/// <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实现服务发现。

本系列未完待续。。。

相关文章
相关标签/搜索