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

本系列未完待續。。。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM