一、Consul 簡介
consul 是什麼
HashiCorp Consul 是一種服務網絡解決方案,它能夠管理服務之間以及跨本地和多雲環境和運行時的安全網絡連接。Consul 它能提供服務發現、服務網格、流量管理和自動更新等功能。
Conslul 提供了一個控制平面,使得你能夠註冊、查詢和保護跨網絡部署的服務。
控制平面作用,它維護一箇中央註冊表來跟蹤服務及其各自的 IP 地址,它是一個分佈式系統,運行在節點集羣上。
Consul 架構
(consul v1.15 的單集羣架構)
Consul 還支持多數據中心架構,具體可以它的文檔:https://developer.hashicorp.com/consul/docs/architecture。
Consul 也提供了 CLI 命令操作功能,https://developer.hashicorp.com/consul/commands。(v1.15.x)
Consul 也提供了 API 的功能,https://developer.hashicorp.com/consul/api-docs。
Consul 還提供了一個簡單的 Web 查看操作界面。
Consul 提供的 API 功能
在這篇文檔中,https://developer.hashicorp.com/consul/api-docs 可以看到 Consul 提供了很多 API 功能。
- KV Store:kv 存儲
- Catalog:/catalog endpoints 在 Consul 中註冊和註銷節點、服務和檢查,catalog 不應該與 agent 混淆,這個 API 方法看起來很相似。
- Agent:/agent endpoints 用於與本地 Consul 代理交互。通常,服務和檢查信息是在代理上註冊,然後由代理負責保持數據與集羣同步。
- Checks:/agent/checks,返回本地代理註冊的所有檢查。這個 endpoints 還有其它很多功能比如 Register Check,Deregister Check,TTL Check Pass,TTL Check Fail,TTL Check Warn,TTL Check Update等。
- Service:/agent/service,這個 endpoints 與 Consul 中本地代理上服務交互。這個不應與 catalog 中的服務相混淆。它也有很多功能比如 Register Service,Deregister Service,Get local service health 等。更多功能請查看文檔。
- Config:在 /config, 這個 endpoints 創建、刪除、更新和查詢向 Consul 註冊的配置條目信息
- Health:這個 endpoints 查詢與檢查相關的信息。它是與 catalog 分開提供功能,因爲用戶可能不喜歡使用可選的健康檢查機制。此外,這個 endpoints 提供查詢信息時,會把 catalog 提供的原始信息過濾掉一些信息。
- Event:觸發新事件,查詢可用的事件。
- Operator:/operator, 這個 endpoints 爲用戶提供了一個查詢集羣信息的工具。比如與 Raft 子系統交互。
- Sessions:/session 提供操作 session 的功能。
- ACLs 管理基於ACL的令牌和策略
- Policies:/acl/policy,ACL Policy HTTP API,在 Consul 中創建、讀取、更新、列出和刪除 ACL 策略。
- Tokens:/acl/token,endpoints 提供了在 Consul 中創建、讀取、更新、刪除 ACL token 等功能。
- ACL Auth Method:/acl/auth-method。
- Roles:/acl/role,操作 role 功能。
- ACL Binding Rule:/acl/binding-rule,在 Consul 中操作 binding-rule。
更多 API 功能請查看文檔:https://developer.hashicorp.com/consul/api-docs。
Consul 提供的 API SDK
Go API SDK:https://github.com/hashicorp/consul/tree/release/1.15.0/api
二、Consul 服務註冊和發現
在上面 Consul 介紹中,它提供了很多 API 功能,與服務註冊和發現功能相關的 API 有 agent、health 還有 kv store 等等,最重要的是 agent。
如果涉及到權限還可以用 ACL 功能。
高可用方面,Consul 不僅提供了單集羣架構,也提供了多數據中心集羣架構。
不過 Consul 都給我們提供了相關 API。
服務註冊和發現相關API
相關文檔:
https://developer.hashicorp.com/consul/docs/concepts/service-discovery (v1.15.x),對服務註冊發現原理解釋
https://developer.hashicorp.com/consul/api-docs/agent/service,Agent HTTP API,操作服務的API
- List Services,列出所有的服務
- Register Service,註冊服務實例,向本地代理註冊新的服務實例,也可以包含健康檢查
- Deregister Service,刪除服務實例。負責刪除目錄中的服務,如果有關聯服務則一併刪除
- Get Service Configuration,本地代理上註冊的單個服務完整服務實例信息
- Get local service health,根據名稱查詢本地代理上所有服務實例狀態
- Get local service health by ID,同過 ID 查詢服務實例健康狀態
- Enable Maintenance Mode,啓用維護模式,在維護模式期間,該服務將被標記爲不可用,並且不會出現在 DNS 或 API 查詢中。維護模式是持久的,將在代理重新啓動時自動恢復。
- Methods to Specify Namespace,指定命名空間(企業版纔有功能)
服務發現使用的是服務名來標識服務發現的信息,而不是傳統使用的 IP 地址和端口。允許你動態映射服務並跟蹤服務信息的任務變更。
- 服務註冊
服務實例註冊使用 IP 地址和端口將自身服務信息註冊到服務目錄。當你註冊的新實例註冊到服務目錄(the service catalog)時,他們將參與負載均衡池以處理服務消費者請求。
(來自:consul 文檔service-discovery)
- 服務發現(查詢)
服務的消費者可以通過註冊的實例服務名來查詢相關服務信息。
(來自:consul 文檔service-discovery)
- 服務更新
隨着新服務實例的添加或舊的不健康服務實例的刪除,服務目錄會動態更新。移除的服務將不再參與服務消費者請求的負載均衡池。
(來自:consul 文檔service-discovery)
服務發現的2種主要類型
兩種主要的服務發現類型:
1、client-side discovery 客戶端發現模式
2、server-side discovery 服務端發現模式
- 客戶端發現
客戶服務發現模式中,服務消費者負責確定可用服務實例的訪問信息以及他們之間的負載均衡請求。
一般步驟:
- 服務消費者查詢服務目錄
- 對服務目錄進行檢索並返回所有的服務信息
- 服務消費者選擇健康的服務實例並直接向其發送請求
(來自:consul 文檔service-discovery)
- 服務端發現
服務端服務發現模式,服務消費者使用中介來查詢服務目錄並向他們發送請求。步驟如下:
- 服務消費者查詢中介(Consul)
- 中介查詢服務目錄並將請求路由到可用的服務實例
(來自:consul 文檔service-discovery)
三、代碼實現服務註冊和發現
安裝 Consul
安裝地址:consul intall。
也可以直接用 docker 安裝:docker pull consul
。
好多年前我也寫了一個 consul 的基本配置和使用:https://www.cnblogs.com/jiujuan/p/9356772.html。
因爲我用的 win,直接下載 bin 安裝了。
啓動用 dev 模式:consul agenet -dev
。
代碼例子
關於服務發現和註冊的 2 個主要 API 文檔:
在 /agent/service/register 這個 API 裏,請求的 Body 裏定義了一些 JSON 類型參數,
比如 Name,ID,Tags,Address,TaggedAddress,Meta,Port,Checks,Weights 等等參數,可以去文檔看看。
一個最簡單的服務信息 json格式:
{
"service": {
"id": "hello-service",
"name": "hello-service",
"tags": ["test"],
"port": 80,
"address": "127.0.0.1",
}
}
例子代碼:
consul: v1.15.0
consul api: github.com/hashicorp/consul/api v1.18.0
go: v1.18
- 添加服務實例接口和查詢服務接口
import consulapi "github.com/hashicorp/consul/api"
// 添加服務實例接口
type Registry interface {
Register(string, string, string, string, int) error
Deregister(string) error
}
type Service interface {
GetServices() (map[string]*consulapi.AgentService, error)
GetService(string) (*consulapi.AgentService, *consulapi.QueryMeta, error)
FilterService(string) (map[string]*consulapi.AgentService, error)
}
- consul客戶端配置和鏈接
type client struct {
client *consulapi.Client
}
func NewConfig(addr string) *consulapi.Config {
config := consulapi.DefaultConfig()
if addr != "" {
config.Address = addr
}
return config
}
func NewClient(addr string) (*client, error) {
config := NewConfig(addr)
c, err := consulapi.NewClient(config)
if err != nil {
return nil, err
}
return &client{client: c}, nil
}
- 註冊服務實例
func (c *client) Register(name, id, tags, ip string, port int) error {
// 服務註冊信息
reg := &consulapi.AgentServiceRegistration{
ID: id, // 唯一服務 ID
Name: name, // 服務名
Tags: []string{tags}, // 標籤,可以標識相同服務
Port: port, // 端口號
Address: ip, // 所在節點 ip 地址
}
// 服務健康檢查
reg.Check = &consulapi.AgentServiceCheck{
HTTP: fmt.Sprintf("http://%s:%d%s", ip, port, "/health-check"), // http形式檢查,健康檢測/health-check路徑
Timeout: "5s",
Interval: "5s", // 每隔5s檢查一次
DeregisterCriticalServiceAfter: "40s", // 服務40s不可達時,註銷服務
Status: "passing", // 默認服務狀態正常
}
return c.client.Agent().ServiceRegister(reg)
}
然後添加獲取服務的函數 GetService(),最後在 main 函數裏寫測試例子,完整代碼請查看 github:
寫完後運行:go run registry.go
,然後在瀏覽器上查看註冊的服務實例,http://localhost:8500/ui/dc1/services,註冊服務成功(如下圖).
如果停掉運行的 go run registry.go,它會在 40s 後自動註銷服務實例,40s 後查看頁面,註冊的2個服務實例已經被刪除。
完整代碼
完整代碼例子:github registry
四、go-kratos中的consul
主要接口分析
go-kratos 爲服務註冊和發現抽象了 2 個最重要的接口。
- 註冊服務接口:
// https://github.com/go-kratos/kratos/blob/v2.5.4/registry/registry.go
// Registrar is service registrar
type Registrar interface {
// 註冊實例
Register(ctx context.Context, service *ServiceInstance) error
// 註銷實例
Deregister(ctx context.Context, service *ServiceInstance) error
}
- 發現服務接口:
// https://github.com/go-kratos/kratos/blob/v2.5.4/registry/registry.go#L17
// Discovery is service discovery
type Discovery interface {
// 根據 serviceName 直接拉取實例列表
GetService(ctx context.Context, serviceName string) ([]*ServiceInstance, error)
// 根據 serviceName 阻塞式訂閱一個服務的實例列表信息
Watch(ctx context.Context, serviceName string) (Watcher, error)
}
還有一個 watcher 接口
// https://github.com/go-kratos/kratos/blob/v2.5.4/registry/registry.go#L25
type Watcher interface {
// Next returns services in the following two cases:
// 1.the first time to watch and the service instance list is not empty.第一次查看服務列表是否爲空
// 2.any service instance changes found. 發現服務實例改變
// if the above two conditions are not met, it will block until context deadline exceeded or canceled
Next() ([]*ServiceInstance, error)
// Stop close the watcher.
Stop() error
}
Consul 對 go-kratos 中定義服務註冊和發現接口的實現在 contrib/registry/consul 目錄裏:
- consul registry struct:
kratos 中的 Registry struct
- consul Client struct:
kratos 中的 Client struct
代碼例子
go-kratos 官方代碼例子,client/main.go:
func main() {
consulClient, err := api.NewClient(api.DefaultConfig()) // consul client
if err != nil {
panic(err)
}
r := consul.New(consulClient) // 把 consulClient 客戶端連接添加到 go-kratos 中的registry
// grpc client
conn, err := grpc.DialInsecure(
context.Background(),
grpc.WithEndpoint("discovery:///helloworld"),
grpc.WithDiscovery(),
)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
gClient := helloworld.NewGreeterClient(conn) // gRPC 方式調用 helloworld 中方法
// http client
hConn, err := http.NewClient(
context.Background(),
http.WithMiddleware(
recovery.Recovery(),
),
http.WithEndpoint("discovery:///helloworld"), // 服務名
http.WithDiscovery(r), // 這裏用 consul 作爲服務發現中心
)
if err != nil {
log.Fatal(err)
}
defer hConn.Close()
hClient := helloworld.NewGreeterClient(hConn) // http 方式調用 helloworld 中方法
for {
time.Sleep(time.Second)
callGRPC(gClient)
callHTTP(hClient)
}
}
go-kratos 官方代碼例子,server/main.go:
func main() {
logger := log.NewStdLogger(os.Stdout)
log := log.NewHelper(logger)
// consul client
consulClient, err := api.NewClient(api.DefaultConfig())
if err != nil {
log.Fatal(err)
}
r := consul.New(consulClient) // 把 consulClient 客戶端連接添加到 go-kratos 中的registry
// http server
httpSrv := http.NewServer(
http.Address(":8000"),
http.Middleware(
recovery.Recovery(),
logging.Server(logger),
),
)
// grpc server
grpcSrv := grpc.NewServer(
grpc.Address(":9000"),
grpc.Middleware(
recovery.Recovery(),
logging.Server(logger),
),
)
s := &server{}
helloworld.RegisterGreeterServer(grpcSrv, s) // grpc 方式調用方法
helloworld.RegisterGreeterHTTPServer(httpSrv, s) // http 方式調用方法
app := kratos.New(
kratos.Name("helloworld"),
kratos.Server(
grpcSrv,
httpSrv,
),
kratos.Registrar(r), // 這裏用 consul 作爲服務發現中心
)
if err := app.Run(); err != nil {
log.Fatal(err)
}
}
完整代碼請查看 github:https://github.com/jiujuan/go-kratos-demos/tree/master/registry/consul
五、參考
- https://developer.hashicorp.com/consul/docs Consul文檔(v1.15.x)
- https://developer.hashicorp.com/consul/api-docs Consul API Overview(v1.15.x)
- https://developer.hashicorp.com/consul/tutorials consul tutorial
- https://developer.hashicorp.com/consul/api-docs/agent/service api agent service
- https://github.com/hashicorp/consul/tree/release/1.15.0/api api SDK
- https://developer.hashicorp.com/consul/api-docs/agent/service#json-request-body-schema JSON Request Body Schema
- https://developer.hashicorp.com/consul/docs/concepts/service-discovery consul service discovery
- https://developer.hashicorp.com/consul/docs/install consul install
- https://github.com/go-kratos/kratos/blob/v2.5.4/contrib/registry/consul/
- https://github.com/go-kratos/examples kratos examples
- https://github.com/jiujuan/go-exercises/tree/main/registerservices 完整代碼例子