叢集化工具選擇性很多,這裡選 Consul 工具;官網:https://www.consul.io
本篇計劃用 Docker 輔助部署,所以需要了解點 Docker 知識;官網:https://www.docker.com
Consul 是由N多個節點(臺機/虛機/容器)組成,每個節點中都有 Agent 執行著,各節點間用RPC通訊,所有節點內相同的 Datacenter 名稱為一個資料中心,節點又分三種角色 Client/Server/Leader:
預設埠:
整體架構示意圖:
圖解:任意的 應用服務 Join 到任意的 Consul Node;任意的 Client Join 到任意的 Server;Node之間資料共用。
Consul 中的服務 與 註冊的應用服務
Consul Node Server:是組成 Consul 整體執行的不可缺少的一種節點角色,註冊於 Catalog 中,不如後續叫<節點服務>
Agent Register Service:是被 Consul 管理、發現、健康檢測的目標業務應用,註冊於 Agent 中,不如後續叫<應用服務>
作者:[Sol·wang] - 部落格園,原文出處:https://www.cnblogs.com/Sol-wang/p/17296278.html
所有的應用服務,都向 Consul 報告自己的存在及具體的資訊;
新應用服務的加入,通過Client/Server上報給上級,直至Leader;Leader再向所有Server廣播新服務的存在及具體資訊,Consul 中所有節點共用新加入服務的資訊;其中包括應用服務本身的連線及健康檢測資訊。
任何登出的應用服務,Consul 也會同步到各節點,關聯的健康檢測一併登出。
作者:[Sol·wang] - 部落格園,原文出處:https://www.cnblogs.com/Sol-wang
Consul向各應用服務發起的連線過程,為了提供所有健康可用的應用服務,按提供的檢測方式、檢測地址、檢測頻率等,發起通訊檢測,識別服務狀態,踢除異常及不可用的範例,保留健康可用的範例,並把結果上報給 Consul-Server/Leader。
檢測方式分為:script / http / tcp / udp / ttl / rpc 等
比如:指令碼在各服務上的執行反饋
比如:各服務提供HTTP請求的API
比如:各服務提供TCP連線的埠
在叢集內外,任何想要連線叢集內應用服務的資訊,先通過 Consul-Server 拉取到健康可用的應用服務資訊,才能連線到指定的應用服務;
新應用服務不斷的註冊/宕機/登出等,每個時間段所提供的各應用服務資訊都可能是變化的;
這種 Consul-Server 提供的健康服務地址列表的過程,給出了所有可用的應用服務資訊,就叫做服務發現。
比如:Nginx需要知道[訂單服務]的存取IP埠資訊,才好轉發請求
比如:[訂單服務]需要請求[產品服務]API,Consul提供了所有健康的[產品服務]存取IP埠資訊,[訂單服務]才能請求到[產品服務]API
按[訂單]服務查詢出健康可用的應用服務列表,遮蔽了異常狀況的應用服務,達到了 故障轉移 的效果。
動態的、可維護的、持久化的、鍵值對的儲存方式;比較獨立的一項Consul功能,我們可以把需要動態的內容放入KV中儲存,它就像庫一樣,隨時可變更查詢。
key 唯一鍵;value 對應值;flags 64位元整數可選值
GET 查詢/列表
# 命令列 查詢全部
consul kv get -recurse
# 命令列 查詢單個[列表]
consul kv get [-detailed] {key}
# API 查詢全部
curl http://{host}:8500/v1/kv/?recurse
# API 查詢單個
curl http://{host}:8500/v1/kv/{key}
PUT 新增/修改
# 命令列 新增/修改
consul kv put [-flags=13] {key} {value}
# API 新增/修改
curl -X PUT -d '{value}' http://{host}:8500/v1/kv/{key}[?flag=13]
DELETE 刪除/全部
# 命令列 刪除一個
consul kv delete {key}
# 命令列 刪除列表(全部)
consul kv delete -recurse [key/prefix]
# API 刪除列表(全部)
curl -X DELETE http://{host}:8500/v1/kv/{key/prefix}[?recurse]
更多相關API參考:https://developer.hashicorp.com/consul/api-docs/kv
資料中心算是一個概念吧。。。
以上幾點內容大致體現了 Consul 的運作方式,綜合起來也就是一個範圍叢集的說法,其中會按 Datacenter 名稱的不同,區分為多個資料中心。比如在不同的地域提供不同的資料中心,或者相同的資料中心互通,以做候選備用等。
在叢集中,每種應用服務都可能不止一個執行範例,訂單服務A呼叫產品服務B,通過ConsulAPI給出的產品服務B可用地址會是多個,同樣都是產品服務,有的資源已用90%,有的資源才用10%,為了避免這種資源利用不均勻,如何做到負載均衡呢?
常用方式:隨機、輪詢、最小連線、權重 等。
當然,Consul未提供此功能,或用第三方或自己編寫實現;
比如:寫一個負載均衡的類庫,每個服務已連線次數記錄在 Consul 的 KV 中,呼叫方給出要呼叫的服務組名,使用類庫得出本次要請求的具體服務地址等。
Consul 並沒有提供這樣的功能,作為叢集中不可忽視的點,這裡只有粗略敘述,以作了解。
熔斷:存在於請求方與應用服務之間,當應用服務異常次數達到指定值,下次請求就在熔斷處直接返回,不用再連線到異常應用服務上。
降級:也就是備用方案的啟用;比如:DB異常時,用快取的資料;快取異常時,或保留請求資訊做延遲處理;或預設資料的返回等。
對於叢集的災難與備份,上述有提到多資料中心同步可達到備份的效果,Consul 的快照方式也是一個可選項:
# 命令列 生成快照
consul snapshot save {backup-name}.snap
# API 生成快照
curl http://{host}:8500/v1/snapshot?dc={dc-name} --output {backup-name}.snap
# 命令列 恢復快照
consul snapshot restore {backup-name}.snap
# API 恢復快照
curl -X PUT --data-binary @{backup-name}.snap http://{host}:8500/v1/snapshot
# 命令列 快照詳細
consul snapshot inspect {backup-name}.snap
定期生成快照consul snapshot agent
僅企業版可用
以下用 docker 部署,docker 拉取映象:docker pull consul
不管是 Client / Server / Leader 哪種角色,都是 Agent 執行起來的 Node;以下通過兩種方式來建立維護 Consul Node:
Consul 中的每個節點都是用 Agent 執行的,建立節點的命令格式如下:
docker run -d --name={容器名稱} -p 8500:8500 {image} agent -server -ui -node={節點名稱} -bootstrap-expect=3
下表列出了常用各引數的作用說明:
agent | 必須;Consul 的應用,於每個節點中 |
-server | 必須,服務角色;無:被視為Client角色 |
-node | 必須;本節點名稱 |
-bootstrap-expect | 必須;定義Server角色的數量,必須夠數,才能成為一個叢集,否則叢集不會執行 |
-datacenter | 資料中心名稱(群名稱),預設 dc1 |
-join | 加入的節點IP地址(Client/Server) |
-retry-join | 嘗試重新加入時的節點IP(Client/Server) |
-data-dir | 指定執行時的資料存放目錄 |
-config-file | 使用指定的組態檔啟動執行(檔案內容與此表引數項作用相似) |
-ui | 帶管理Web頁面;存取伺服器IP http://{ip}:8500 進入頁面管理方式 |
-client | 連線限制,開放連線的使用者端;瀏覽器連線開啟UI、Client連線Server等 |
下面來部署一個 Consul Datacenter,計劃有3個 Server 節點,3個 Client 節點。
建立3個 Server 節點
節點名稱分別定為:ser-a / ser-b / ser-c;由於 Datacenter 的預設值都是 dc1,所以就形成了一個名為 dc1 的資料中心。
# 第一個 Server Node,所以 Consul 會預設為 Leader 角色
docker run -d --name=cons-ser-a -p 8501:8500 consul agent -server -ui -node=ser-a -bootstrap-expect=3 -client=0.0.0.0
# 以下的 Server Node 都 Join 到 Leader Node 上
docker run -d --name=cons-ser-b -p 8502:8500 consul agent -server -node=ser-b -ui -client=0.0.0.0 -retry-join=172.17.0.2
docker run -d --name=cons-ser-c -p 8503:8500 consul agent -server -node=ser-c -ui -client=0.0.0.0 -retry-join=172.17.0.2
起初建立 Leader Node 時bootstrap-expect=3
,現在已經執行了3個 Server Node;
所以 Consul 已經啟動完成並運轉;可以開啟UI介面:http://{宿主機IP}:8501/
再建立3個 Client 節點,並加入到不同的 Server Node
docker run -d --name=cons-cli-a -p 8511:8500 consul agent -node=cli-a -client=0.0.0.0 -retry-join=172.17.0.2
docker run -d --name=cons-cli-b -p 8512:8500 consul agent -node=cli-b -client=0.0.0.0 -retry-join=172.17.0.3
docker run -d --name=cons-cli-c -p 8513:8500 consul agent -node=cli-c -client=0.0.0.0 -retry-join=172.17.0.4
以上節點的建立過程,是用宿主機的不同埠對映到各自容器節點的8500埠,所以用支援UI的對應宿主機埠都可以開啟UI介面
計劃的所有 Consul 節點建立完成,Consul 就是用這些節點來管理應用服務叢集的,應用服務等後續再加入;以下先闡述節點的維護
檢視 Datacenter 成員
# 命令列 列出所屬 Datacenter 中的全部成員 [詳細]
docker exec -t {容器名稱} consul members [-detailed]
# 命令列 列出所屬 Datacenter 中的 Server 角色成員
docker exec -t {容器名稱} consul operator raft list-peers
Server 加入到 Leader 下
# 如果要加 -datacenter 的話,必須與 join 引數目標的dc名稱一致
docker run -d --name={容器名稱} {image} agent -server -node={node-name} -join={leader-ip}
Client 加入到 Server 下
# 建立時加入
docker run -d --name={容器名稱} {image} agent -datacenter={有要一致} -node={name} -join {server-ip}
# 建立後加入
docker exec -t {容器名稱} consul join {server-ip}
下線指定節點
# 命令列 移除所處節點
consul leave
# 命令列 強制移除指定節點 [清楚未執行的節點]
consul force-leave {node-name} [-prune]
不止命令列方式,Consul 也提供了 API 方式來管理節點。
# API 列出所有成員
curl http://{host}:8500/v1/catalog/nodes
# API 加入新節點
# 引數檔案 json 指明瞭節點名稱/地址/埠等必要項
curl -X PUT -d @cli-d.json http://{host}:8500/v1/catalog/register
# API 登出節點
curl -X PUT -d '{"Datacenter":"dc1","Node":"cli-d"}' http://{host}:8500/v1//catalog/deregister
有沒有麻煩了點。。。 這種方式應該用的不多吧。。。
Consul 6個節點已部署完成,接下來該在 Consul Node 上部署微服務了。
Consul Node 部署完成以後呢,剩下的就是告知 Consul 有哪些應用服務需要你管理,這告知 Consul 的方式有以下幾種:
假設應用服務已經執行起來,然後按 Consul 定義的應用服務設定格式,編寫組態檔,放到 Consul 任意節點的設定目錄下,檔名稱自定義,每次 Consul 啟動的時候,都會讀取設定目錄下的所有檔案。
設定內容也就是告訴 Consul 我是誰、我在哪、怎麼與我聯絡、我的檢測方式等;
1、設定範例格式如下:
{
"service": [
{
// 服務身份定義
"id": "AppService-Redis-aaaaa-bbbb-cccc-dddd-eeeee",
"name": "AppService-Redis",
"port": 80,
// 健康檢測定義
"check": {
"id": "AppService-Redis-Check-TCP",
"name": "Redis Check TCP on port 80",
"tcp": "172.17.0.10:80",
"interval": "10s",
"timeout": "3s"
}
}
]
}
2、過載此節點設定:docker exec -t cons-ser-a consul reload
至此完成應用服務註冊;如下效果圖:
以上組態檔:有 Service 項 就是 服務註冊,有 Check 項 就是 健康檢測註冊。
那。。。登出健康檢測呢?登出服務呢?
刪除 Check 項 就是登出健康檢測;刪除檔案 就是 登出服務;記得重新載入設定consul reload
幾乎命令列能做的事情,Consul 提供的 API 方式也可以做到。
假設應用服務已經執行起來了,同樣是編寫組態檔,以傳參的方式請求註冊的 API,完成 服務註冊、健康註冊、服務登出、健康登出等。
以下案例準備了服務註冊所需的引數檔案:AppService-kafka.json
{
"service": [
{
// 服務身份定義
"id": "AppService-Kafka-xxxxx-yyyy-zzzz-wwww-vvvvv",
"name": "AppService-Kafka",
"port": 80
}
]
}
帶檔案引數請求註冊服務的API介面:
curl -X PUT -d @AppService-kafka.json http://{host}:8500/v1/agent/service/register
當然,也可以單獨請求註冊健康檢測的API介面:
curl -X PUT -d @AppService-health.json http://{host}:8500/v1/agent/check/register
同樣的,API登出介面:
curl -X PUT http://{host}:8500/v1/agent/service/deregister/{app-service-id}
curl -X PUT http://{host}:8500/v1/agent/check/deregister/{app-check-id}
更所相關API介面參考官網:https://developer.hashicorp.com/consul/api-docs
1、建立 .NET 專案,並啟用 Docker 方式,假設叫[訂單服務],Nuget 安裝 Consul
[訂單服務]中必須要完成開發的事情:服務註冊動作,健康檢測定義,服務登出動作。
其實就是把 Consul 類庫相關的引數賦值,由 Consul 類庫自動完成註冊/檢測/登出。
2、appsettings 相關設定:
"Consul": {
"Service-Name": "AppService-Order",
"Service-Port": 80,
"Service-Health": "/Health",
// 應用服務 Join to Node Address
// 未來 Join 的 Node 不固定;所以 建立容器時 再傳參
"Register-Address": null
}
3、擴充套件 IApplicationBuilder 實現 註冊/檢測/登出 並啟用
以下擴充套件方法建立後,並在管道中啟用:app.UseConsul(app.Configuration, app.Lifetime);
public static IApplicationBuilder UseConsul(this IApplicationBuilder app, IConfiguration conf, IHostApplicationLifetime lifetime)
{
// 取本機IP(告訴 Consul 健康檢測的地址)
string? _local_ip = NetworkInterface.GetAllNetworkInterfaces()
.Select(p => p.GetIPProperties())
.SelectMany(p => p.UnicastAddresses)
.FirstOrDefault(p =>
p.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(p.Address)
)?.Address.ToString();
// 指明註冊到的 Consul Node
var client = new ConsulClient(options =>
{
options.Address = new Uri(conf["Consul:Register-Address"]);
});
// 應用服務的 服務資訊 / 健康檢測
var registration = new AgentServiceRegistration
{
Name = conf["Consul:Service-Name"],
ID = $"Order-{Guid.NewGuid().ToString()}",
Address = _local_ip,
Port = Convert.ToInt32(conf["Consul:Service-Port"]),
Check = new AgentServiceCheck
{
Timeout = TimeSpan.FromSeconds(5),
Interval = TimeSpan.FromSeconds(10),
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),
HTTP = $"http://{_local_ip}:{conf["Consul:Service-Port"]}{conf["Consul:Service-Health"]}"
}
};
// 應用服務啟動時 - 註冊
lifetime.ApplicationStarted.Register(() =>
{
client.Agent.ServiceRegister(registration).Wait();
});
// 應用服務停止時 - 登出
lifetime.ApplicationStopping.Register(() =>
{
client.Agent.ServiceDeregister(registration.ID).Wait();
});
return app;
}
4、編寫健康檢測 API
這裡健康檢測選用 HTTP API 方式;為此,需要編寫 WebApi。
# 服務中追加健康檢測介面,供 Consul 呼叫
# 與 appsettings 設定保持一致的控制器
# 比較簡單,達到通訊正常,就被視為服務執行正常
public class HealthController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok();
}
}
5、生成 Docker 映象並執行
每個應用服務都會執行多個範例,把已開發的[訂單服務]用Docker執行多個範例,模仿叢集環境:
# 專案根目錄 生成 docker 映象
docker build -t order.serv.cons:dev -f ./Order-Web/Dockerfile .
# docker 執行 [訂單服務],這裡執行2個吧(多範例)
# 還記得 appsettings 中的註冊節點地址麼...這裡傳參指明註冊的節點
docker run -d --name=order-serv-cons.a -p 5001:80 order.serv.cons:dev --Consul:Register-Address='http://172.17.0.6:8500'
docker run -d --name=order-serv-cons.b -p 5002:80 order.serv.cons:dev --Consul:Register-Address='http://172.17.0.7:8500'
執行2個 [訂單服務] 後的 Consul-UI 圖示:
新的服務 AppService-Order 下有兩個執行健康的範例,註冊與檢測都是類庫幫助實現的,並顯示出IP及註冊節點。
相同的服務,有多個執行範例;不同的服務,組成完整的微服務叢集;被 Consul 的6個 Node 時時管理著。
測試服務發現
1、Consul API 方式 指定服務的健康範例查詢:http://{host}:8501/v1/health/service/{service-name}?passing
2、Consul 類庫方式 拉取指定服務的健康範例地址:
// 指明一個節點地址
var _consul_node = new ConsulClient(options =>{ options.Address = new Uri(_conf["Consul:Node-Address"]); });
// 向 Consul 拉取 指定服務 健康範例的列表
var _order_result = await _consul_node.Health.Service("AppService-Order", null, true, _query_options);
var _order_instance_list = _order_result.Response.Select(i => $"http://{i.Service.Address}:{i.Service.Port}");
服務異常測試
docker 停止一個容器後,只剩一個執行範例:
當然,docker 容器再啟動後,範例還會再回來。
服務間的相互呼叫,X服務 -> 訂單服務,通過以上方式得出多個可用的範例地址,假如用輪詢方式實現了負載均衡;
後續也可以把各應用服務的可用列表,時時的提供給閘道器(如Nginx),實現閘道器中的負載均衡。
consul connect proxy:叢集代理,通過呼叫代理者的埠,實現與被代理者的連線(像是兩臺機的埠對映似的,被代理者埠不被暴露)
Consul Service Mesh:在多個叢集和環境間建立連線,建立全球化的跨平臺服務網路;下有多個Mesh Gateway,每個Mesh Gateway 下是 Datacenter。
待續...