Go微服务框架go-kratos实战学习07:consul 作为服务注册和发现中心
一、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
也欢迎到我的公众号 九卷技术录:go-kratos实战学习07: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 完整代码例子