微服務 - 叢集化 · 服務註冊 · 健康檢測 · 服務發現 · 負載均衡

Sol·wang發表於2023-04-07

叢集化工具選擇性很多,這裡選 Consul 工具;官網:https://www.consul.io

本篇計劃用 Docker 輔助部署,所以需要了解點 Docker 知識;官網:https://www.docker.com

一、Consul 概括

Consul 是由N多個節點(臺機/虛機/容器)組成,每個節點中都有 Agent 執行著,各節點間用RPC通訊,所有節點內相同的 Datacenter 名稱為一個資料中心,節點又分三種角色 Client/Server/Leader:

  • Agent:Consul 各成員節點的執行載體
  • Client:不是必須存在的角色,數量也無上限,少量的資源開銷,建議更多的 Client 角色存在。
  • Server:必須的Server角色,每個Server下可多個Client,可以代替Client,對接收到的資訊持久化;資源開銷大,官方建議3/5臺。
  • Leader:一個資料中心內的 Server 選舉產生一個 Leader 角色,將資訊下發廣播給所有 Server

Consul 中的預設埠

  • 8500:Consul 對外提供註冊查詢UI等的專用埠
  • 830x:Consul 內各節點間TCP/RPC等通訊的專用埠
  • 8600:Consul DNS 使用
  • 21xxx:Consul 自動分配代理使用

Consul 整體執行示意圖

圖解:任意的 應用服務 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 功能

服務註冊

所有的應用服務,都向 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

按[訂單]服務查詢出健康可用的應用服務列表,遮蔽了異常狀況的應用服務,達到了 故障轉移 的效果。

K/V儲存

動態的、可維護的、持久化的、鍵值對的儲存方式;比較獨立的一項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

Datacenter

資料中心算是一個概念吧。。。

以上幾點內容大致體現了 Consul 的運作方式,綜合起來也就是一個範圍叢集的說法,其中會按 Datacenter 名稱的不同,區分為多個資料中心。比如在不同的地域提供不同的資料中心,或者相同的資料中心互通,以做候選備用等。

三、Consul 相關

負載均衡

在叢集中,每種應用服務都可能不止一個執行例項,訂單服務A呼叫產品服務B,透過ConsulAPI給出的產品服務B可用地址會是多個,同樣都是產品服務,有的資源已用90%,有的資源才用10%,為了避免這種資源利用不均勻,如何做到負載均衡呢?

常用方式:隨機、輪詢、最小連線、權重 等。

  • 隨機方式:實現起來比較簡單,在拉取到的應用服務資料列表中,隨意挑一個使用就好
  • 輪詢方式:需要有個全域性變數,記錄當前已用到哪個地址了,下標+1的方式取列表中下個健康的地址
  • 最小連線:記錄每個應用服務例項當前已產生多少連線,每次使用最小已連線的例項做為本次的連線
  • 權重方式:配置應用服務例項在整體服務中所佔的使用比例上限,每次連線後計算更新已連線的佔比

當然,Consul未提供此功能,或用第三方或自己編寫實現;

比如:寫一個負載均衡的類庫,每個服務已連線次數記錄在 Consul 的 KV 中,呼叫方給出要呼叫的服務組名,使用類庫得出本次要請求的具體服務地址等。

熔斷降級

Consul 並沒有提供這樣的功能,作為叢集中不可忽視的點,這裡只有粗略敘述,以作了解。

熔斷:存在於請求方與應用服務之間,當應用服務異常次數達到指定值,下次請求就在熔斷處直接返回,不用再連線到異常應用服務上。

降級:也就是備用方案的啟用;比如:DB異常時,用快取的資料;快取異常時,或保留請求資訊做延遲處理;或預設資料的返回等。

Snapshot

對於叢集的災難與備份,上述有提到多資料中心同步可達到備份的效果,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僅企業版可用

四、Consul 部署

以下用 docker 部署,docker 拉取映象:docker pull consul

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 方式管理節點成員

# 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

API 方式註冊服務

幾乎命令列能做的事情,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:Service Mesh & Gateways

consul connect proxy:叢集代理,透過呼叫代理者的埠,實現與被代理者的連線(像是兩臺機的埠對映似的,被代理者埠不被暴露)

Consul Service Mesh:在多個叢集和環境間建立連線,建立全球化的跨平臺服務網路;下有多個Mesh Gateway,每個Mesh Gateway 下是 Datacenter。

待續...

相關文章