第二節:Consul簡介及服務注冊、發現、健康檢查


一. 簡介

本節架構圖:

(PS:該圖僅服務於本節,完整版的微服務架構圖見后最后章節)

 

 

1. 什么是Consul?

  Consul是一個用來實現分布式系統的服務發現與配置的開源工具,它的可以實現服務提供者 和 服務消費者的隔離,比如:比如服務提供者(GoodsService)將自身注冊到Consul中, 注冊的信息是:ServiceName + ip/port,這樣服務消費者只需要知道ServiceName就可以知道對應服務的ip+端口,從而進行訪問,就好比DNS的功能。

PS. 和Consul同類的還有:zookeeper、etcd、Eureka。

 

2. Consul的好處

  同一個ServiceName下可以對應多個 ip+port,只要ServiceID不同即可,也就是說同一個項目多次注冊到同一個ServiceName下,這樣消費者通過ServiceName可以拿到其中一個或者多個,可以在消費者層次書實現負載均衡,即客戶端的負載均衡。另外服務消費者不需要記住多個 ip+port 了,只需要記住一個ServiceName即可,即不需要關心SeviceName下的業務服務器是否增加,是否宕機的問題。

PS:當然客戶端不能直接訪問Consul了,后續會通過Ocelot轉發。 本節這樣的設置,最終消費者還是要訪問業務服務器的,正常情況下業務服務器是不對外開放,而且也不利於做授權校驗,這就需要后續章節Ocelot來解決了。

3. Consul相關信息

 (1).consul的官網:https://www.consul.io/     最新的Server下載地址:https://www.consul.io/downloads

 (2).consul客戶端UI地址:http://127.0.0.1:8500

 

4. Consul的啟動

 (1).開發模式:【consul.exe agent -dev】,開發模式不會持久化數據,重啟之后保存的配置信息就會丟失.

 (2).生產模式:【consul.exe agent -server -bootstrap-expect 1 -data-dir d:/consul/data 】

PS:在D盤創建consul/data文件夾,用於持久化Consul。

 

 (3).客戶端模式:通過.Net程序來啟動

補充Consul啟動的各個參數:

 a. agent:consul的核心命令,主要作用有維護成員信息、運行狀態檢測、聲明服務以及處理請求等

 b. -server:就是代表server模式

 c. -bootstrap-expect:代表想要創建的集群數目,官方建議3或者5

 d. -data-dir:數據存儲目錄

 e. -client:是一個客戶端服務注冊的地址,可以和當前server的一致也可以是其他主機地址,提供HTTP、DNS、RPC等服務,系統默認是127.0.0.1,所以不對外提供服務,如果你要對外提供服務改成0.0.0.0

 f. -bind:集群通訊地址,集群內的所有節點到地址都必須是可達的,默認是0.0.0.0

 g. -node:代表當前node的名稱,節點在集群中的名稱,在一個集群中必須是唯一的,默認是該節點的主機名

 h. -ui:代表開啟web 控制台

 i. -config-dir:配置文件目錄,里面所有以.json結尾的文件都會被加載

 

二. 應用

前提:下面所有的測試都是基於Consul開發模式啟動的,注冊或者消費者都需要Nuget安裝consul程序包,目前最新:【Consul 1.6.1.1】

1.服務注冊

 (1).含義:通俗的來說就是將一個 Api業務服務(比如 OrderService 和 GoodService), 起一個ServiceName,然后對應啟動地址即ip+port對應,注冊到Consul中,供別人通過ServiceName來調用。 一個ServiceName下可以注冊多個 ip+port,即同一個項目可以注冊在一個ServiceName下, 只要ServiceId不同即可.

 (2).以Api項目為例:在Configure管道中進行注冊,這里面動態獲取ip和端口(需要添加 AddCommandLine支持),然后ServiceRegister進行注冊,ServiceDeregister進行注銷, 注銷代碼中寫在 applicationLifetime.ApplicationStopped.Register里,即程序退出的時候注銷Consul中的服務.

 詳細代碼如下:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime applicationLifetime)
    {   
            // 前面core本身的代碼省略
            //---------------一以下是注冊Consul代碼------------------

            string ip = Configuration["ip"];
            int port = Convert.ToInt32(Configuration["port"]);
            string serviceName = "OrderService";
            string serviceId = serviceName + Guid.NewGuid();
            using (var client = new ConsulClient(ConsulConfig))
            {
                //注冊服務到 Consul 
                client.Agent.ServiceRegister(new AgentServiceRegistration()
                {
                    ID = serviceId,//服務編號,不能重復,用Guid 最簡單 
                    Name = serviceName,//服務的名字 
                    Address = ip,//服務提供者的能被消費者訪問的ip 地址(可以被其他應用訪問的地址,本地測試可以用127.0.0.1,機房環境中一定要寫自己的內網ip 地址) 
                    Port = port,// 服務提供者的能被消費者訪問的端口 
                    //Tags      //可以配置一下額外的參數用於傳遞
                    Check = new AgentServiceCheck
                    {
                        DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服務停止多久后注銷
                        Interval = TimeSpan.FromSeconds(10),//健康檢查時間間隔,或者稱為心跳 間隔
                        HTTP = $"http://{ip}:{port}/api/health",//健康檢查地址 
                        Timeout = TimeSpan.FromSeconds(3)   //超時時間
                    }
                }).Wait();//Consult 客戶端的所有方法幾乎都是異步方法,但是都沒按照規范加上       Async 后綴,所以容易誤導。記得調用后要Wait()或者 await
            }
            //程序正常退出的時候從Consul 注銷服務 ,要通過方法參數注入 IHostApplicationLifetime 
            applicationLifetime.ApplicationStopped.Register(() =>
            {
                using (var client = new ConsulClient(ConsulConfig))
                {
                    Console.WriteLine("程序正常退出的時候從Consul 注銷服務 ");
                    client.Agent.ServiceDeregister(serviceId).Wait();
                }
            });
   }
  private void ConsulConfig(ConsulClientConfiguration c)
  {
      c.Address = new Uri("http://127.0.0.1:8500");
  }        

 (3).UI頁面:服務注冊后,可以訪問:http://127.0.0.1:8500, 來直觀的查看注冊情況.

2.服務的健康檢查

 (1).含義:Consul可以提供與給定服務相關的健康檢查(Web服務器返回200 ok)或者本地節點(“內存利用率低於90%”),即心跳檢測,如果服務不通,該服務在n秒內會自動從 Consul中注銷。

 (2).如何配置:在注冊的時候里面有個參數check,即配置AgentServiceCheck對象,比如參數如下:

  A.HTTP:健康檢查的地址,這里僅支持http請求,接口返回ok

  B.DeregisterCriticalServiceAfter:代表服務停止多久后進行注銷

  C.Interval:健康檢查時間間隔,或者稱為心跳 間隔

  D.Timeout:超時時間

詳見代碼配置:

Check = new AgentServiceCheck
{
     DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服務停止多久后注銷
     Interval = TimeSpan.FromSeconds(10),//健康檢查時間間隔,或者稱為心跳 間隔
     HTTP = $"http://{ip}:{port}/api/health",//健康檢查地址 
     Timeout = TimeSpan.FromSeconds(3)   //超時時間
}

健康檢查的api接口(這里用的是Restful風格):

    [Route("api/[controller]")]
    [ApiController]
    public class HealthController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok("ok");
        }
    }

3.服務發現

(1). 含義

  通俗的來說,就是通過消費者可以通過連接Consul獲取到所有注冊在線ServiceName或者指定的ServiceName,然后根據ServiceName拿到其對應的ip+port(1個或多個),從而進行訪問。

(2). 應用

 前提准備:將GoodsService項目注冊在Consul中,服務名是GoodsService,注冊三個端口分別是:7001 7002 7003

            將OrderService項目注冊在Consul中,服務名是OrderService,注冊三個端口分別是:7004 7005 7006

 A. 獲取所有的服務

 B. 獲取指定名稱的服務,然后自己寫一個隨機算法,拿其中一個ip+port:

 C. 獲取指定名稱服務,輪訓策略

 相關代碼:

 using (var consulClient = new ConsulClient(c => c.Address = new Uri("http://127.0.0.1:8500")))
 {

                #region 1.獲取所有登記在策的consul集合
                {
                    var services = consulClient.Agent.Services().Result.Response;
                    foreach (var service in services.Values)
                    {
                        Console.WriteLine($"id={service.ID},name={service.Service},ip={service.Address},port={service.Port} ");
                    }
                }
                #endregion

                #region 2.隨機找一個登記在冊的服務進行連接(客戶端負載均衡-隨機策略)
                {
                    var services = consulClient.Agent.Services().Result.Response.Values.Where(item => item.Service.Equals("OrderService", StringComparison.OrdinalIgnoreCase));
                    Random rand = new Random();
                    int index = rand.Next(services.Count());//[0,services.Count())
                    var s = services.ElementAt(index);
                    Console.WriteLine($"index={index},id={s.ID},service={s.Service},addr={s.Address},port={s.Port}");
                }

                #endregion

                #region 3.輪訓策略(這里只是演示,實際要考慮並發和溢出的問題)
                {
                    Console.WriteLine(0 % 3);  //結果0
                    Console.WriteLine(1 % 3);  //結果1
                    Console.WriteLine(2 % 3);  //結果2
                    Console.WriteLine(3 % 3);  //結果1
                    Console.WriteLine(4 % 3);  //結果2

                    int Num = 0;
                    var services = consulClient.Agent.Services().Result.Response.Values.Where(item => item.Service.Equals("OrderService", StringComparison.OrdinalIgnoreCase));
                    for (int i = 1; i < 6; i++)
                    {
                        int index = Num++ % services.Count();
                        var s = services.ElementAt(index);
                        Console.WriteLine($"第{i}次的地址如下:");
                        Console.WriteLine($"index={index},id={s.ID},service={s.Service},addr={s.Address},port={s.Port}");
                    }
                }
                #endregion

                #region 4.接口測試
                {
                    var services = consulClient.Agent.Services().Result.Response.Values.Where(item => item.Service.Equals("OrderService", StringComparison.OrdinalIgnoreCase));
                    Random rand = new Random();
                    int index = rand.Next(services.Count());
                    var s = services.ElementAt(index);

                    var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();  //等價於以上三句話
                    IHttpClientFactory httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
                    string url2 = $"http://{s.Address}:{s.Port}/api/Buy/pOrder2";
                    var client = httpClientFactory.CreateClient();
                    var content = new StringContent("userId=001&goodId=123456&num=100", Encoding.UTF8, "application/x-www-form-urlencoded");
                    var response = client.PostAsync(url2, content).Result;
                    string result = "";
                    if (response.IsSuccessStatusCode)
                    {
                        result = response.Content.ReadAsStringAsync().Result;
                        Console.WriteLine(result);
                    }
                }
                #endregion

            }
            Console.ReadKey();
}

 運行結果:

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 

 


免責聲明!

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



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