Consul 介紹
在分布式架構(gòu)中,服務(wù)治理是必須面對(duì)的問題,如果缺乏簡單有效治理方案,各服務(wù)之間只能通過人肉配置的方式進(jìn)行服務(wù)關(guān)系管理,當(dāng)遇到服務(wù)關(guān)系變化時(shí),就會(huì)變得極其麻煩且容易出錯(cuò)。
Consul 是一個(gè)用來實(shí)現(xiàn)分布式系統(tǒng)服務(wù)發(fā)現(xiàn)與配置的開源工具。它內(nèi)置了服務(wù)注冊與發(fā)現(xiàn)框架、分布一致性協(xié)議實(shí)現(xiàn)、健康檢查、Key/Value存儲(chǔ)、多數(shù)據(jù)中心方案,不再需要依賴其他工具(比如 ZooKeeper 等),使用起來也較為簡單。
Consul 架構(gòu)

Consul 集群支持多數(shù)據(jù)中心,在上圖中有兩個(gè) DataCenter,他們通過 Internet 互聯(lián),為了提高通信效率,只有 Server 節(jié)點(diǎn)才加入跨數(shù)據(jù)中心的通信。在單個(gè)數(shù)據(jù)中心中,Consul 分為 Client 和 Server 兩種節(jié)點(diǎn)(所有的節(jié)點(diǎn)也被稱為 Agent),Server 節(jié)點(diǎn)保存數(shù)據(jù),Client 負(fù)責(zé)健康檢查及轉(zhuǎn)發(fā)數(shù)據(jù)請求到 Server,本身不保存注冊信息;Server 節(jié)點(diǎn)有一個(gè) Leader 和多個(gè) Follower,Leader 節(jié)點(diǎn)會(huì)將數(shù)據(jù)同步到 Follower,Server 節(jié)點(diǎn)的數(shù)量推薦是3個(gè)或者5個(gè),在 Leader 掛掉的時(shí)候會(huì)啟動(dòng)選舉機(jī)制產(chǎn)生一個(gè)新 Leader。
Consul 集群搭建
這里使用 Docker 搭建 3個(gè) Server 節(jié)點(diǎn) + 1 個(gè) Client 節(jié)點(diǎn),API 服務(wù)通過 Client 節(jié)點(diǎn)進(jìn)行服務(wù)注冊和發(fā)現(xiàn)。
從 Docker Hub 拉取 Consul 鏡像
docker pull consul
啟動(dòng) 3個(gè) Server 節(jié)點(diǎn) + 1 個(gè) Client 節(jié)點(diǎn)
docker-compose.yaml 如下:
version: '3'
services:
cs1:
image: consul
command: agent -server -client=0.0.0.0 -bootstrap-expect=3 -node=cs1 -data-dir=/data
volumes:
- /usr/local/docker/consul/data/cs1:/data
cs2:
image: consul
command: agent -server -client=0.0.0.0 -retry-join=cs1 -node=cs2 -data-dir=/data
volumes:
- /usr/local/docker/consul/data/cs2:/data
depends_on:
- cs1
cs3:
image: consul
command: agent -server -client=0.0.0.0 -retry-join=cs1 -node=cs3 -data-dir=/data
volumes:
- /usr/local/docker/consul/data/cs3:/data
depends_on:
- cs1
cc1:
image: consul
command: agent -client=0.0.0.0 -retry-join=cs1 -ui -node=cc1 -data-dir=/data
ports:
- 8500:8500
volumes:
- /usr/local/docker/consul/data/cc1:/data
depends_on:
- cs2
- cs3
主要參數(shù)說明:
| 參數(shù)名 | 解釋 |
|---|---|
| -server | 設(shè)置為 Server 類型節(jié)點(diǎn),不加則為 Client 類型節(jié)點(diǎn) |
| -client | 注冊或者查詢等一系列客戶端對(duì)它操作的IP,默認(rèn)是127.0.0.1 |
| -bootstrap-expect | 集群期望的 Server 節(jié)點(diǎn)數(shù),只有達(dá)到這個(gè)值才會(huì)選舉 Leader |
| -node | 指定節(jié)點(diǎn)名稱 |
| -data-dir | 數(shù)據(jù)存放位置 |
| -retry-join | 指定要加入的節(jié)點(diǎn)地址(組建集群) |
| -ui | 啟用 UI 界面 |
集群狀態(tài)
(e002ca62ac24 為容器名稱,可通過 docker ps | grep consul 查看,選擇任意一個(gè)即可)
查看節(jié)點(diǎn)狀態(tài)和類型
docker exec -t e002ca62ac24 consul members

當(dāng)前為3 個(gè) Server 類型節(jié)點(diǎn) ,1 個(gè) Client 類型節(jié)點(diǎn)。
查看 Server 節(jié)點(diǎn)類型
docker exec -t e002ca62ac24 consul operator raft list-peers

當(dāng)前為 cs1 為 leader,可以測試將 cs1 stop 觀察 leader 的重新選舉。
通過 http://192.168.124.9:8500 UI 界面查看 Consul 節(jié)點(diǎn)狀態(tài)如下:(192.168.124.9 是 consul 容器外部訪問 IP)

.NET Core 接入 Consul

創(chuàng)建 .NET Core WebAPI(3.1) 服務(wù) ServiceA(2個(gè)實(shí)例) 和 ServiceB
NuGet 安裝 Consul
-
注冊到 Consul 的核心代碼如下(源碼下載):
public static class ConsulBuilderExtensions { public static IApplicationBuilder RegisterConsul(this IApplicationBuilder app, IHostApplicationLifetime lifetime, ConsulOption consulOption) { var consulClient = new ConsulClient(x => { x.Address = new Uri(consulOption.Address); }); var registration = new AgentServiceRegistration() { ID = Guid.NewGuid().ToString(), Name = consulOption.ServiceName,// 服務(wù)名 Address = consulOption.ServiceIP, // 服務(wù)綁定IP Port = consulOption.ServicePort, // 服務(wù)綁定端口 Check = new AgentServiceCheck() { DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服務(wù)啟動(dòng)多久后注冊 Interval = TimeSpan.FromSeconds(10),//健康檢查時(shí)間間隔 HTTP = consulOption.ServiceHealthCheck,//健康檢查地址 Timeout = TimeSpan.FromSeconds(5) } }; // 服務(wù)注冊 consulClient.Agent.ServiceRegister(registration).Wait(); // 應(yīng)用程序終止時(shí),服務(wù)取消注冊 lifetime.ApplicationStopping.Register(() => { consulClient.Agent.ServiceDeregister(registration.ID).Wait(); }); return app; } } -
添加配置如下:
"Consul": { "ServiceName": "service-a", "ServiceIP": "192.168.124.11", // 當(dāng)前服務(wù)訪問 IP "ServicePort": 8000, "ServiceHealthCheck": "http://192.168.124.11:8000/healthCheck", "Address": "http://192.168.124.9:8500" } -
注冊成功結(jié)果如下:
-
ServiceB 調(diào)用 ServiceA 接口
ServiceB 通過 ConsulClient 進(jìn)行服務(wù)發(fā)現(xiàn),獲取到 ServiceA 的地址,然后隨機(jī)請求請求任意一個(gè)實(shí)例,關(guān)鍵代碼如下:
using (var consulClient = new ConsulClient(a => a.Address = new Uri(_consulOption.Address))) { var services = consulClient.Catalog.Service("service-a").Result.Response; if (services != null && services.Any()) { // 模擬隨機(jī)一臺(tái)進(jìn)行請求,這里只是測試,可以選擇合適的負(fù)載均衡框架 var service = services.ElementAt(new Random().Next(services.Count())); var client = _httpClientFactory.CreateClient(); var response = await client.GetAsync($"http://{service.ServiceAddress}:{service.ServicePort}/test/get"); var result = await response.Content.ReadAsStringAsync(); return result; } } 多次調(diào)用結(jié)果如下:

