.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實現服務發現。

本系列未完待續。。。

相關文章
相關標籤/搜索