Golang 微服务实战 - 2. Consul
第二部分需要提到微服务架构中必不可少的功能:服务发现。我在先前做的 Spring Cloud 项目中使用过 Eureka 作为服务发现框架。但现在 Eureka 已经停更一年多,且使用 Go 编写的 Consul 更适合在 Go 环境微服务分布式系统上使用。
以下列出几篇文章,这几篇文章说明了有关服务发现的相关概念,并对当下热门服务发现框架进行了一些对比:《什么是服务发现》,《服务发现框架Consul的使用》,深入了解服务注册与发现
Consul
什么是 Consul ?官方网站的解释如下:
HashiCorp Consul 是一个网络服务解决方案,使团队能够管理服务之间、本地环境、多云环境以及运行时之间的安全网络连接。Consul 提供服务发现、服务网格、流量管理和网络基础设施设备的自动更新。您可以在单个 Consul 部署实例中单独或一起使用这些功能。
安装 Consul
在官方网站下载二进制文件,随后解压至你所指定的文件目录。解压完成后,仅会得到一个二进制文件consul
(Windows 下则是consul.exe
)。将该二进制文件所在目录添加进系统环境变量,并在控制台输入指令consul
以检查安装是否完成。
Consul 常用命令
全部 Consul 控制台命令详情请使用
consul -h
查看
本项目最常用的指令是consul agent
,其主要功能为运行一个 consul 代理。consul agent
最常使用的功能如下:
-h
查看consul agent
的所有额外命令-bind 0.0.0.0
指定 consul 所在机器的 IP 地址。默认值:0.0.0.0。注意,bind 后没有等号。-http-port=8500
consul 自带一个 web 访问的默认端口:8500-client=127.0.0.1
表明哪些机器可以访问 consul ,默认为本机。0.0.0.0 则表示 所有其它机器均可访问。-config-dir=path
该选项用于指定service的配置文件和检查定义所在的位置。通常会指定为某一个路径/consul.d
(通常情况下,.d
表示一系列配置文件存放的目录)-config-file
指定一个要装载的配置文件。该选项可以配置多次,进而配置多个配置文件。-data-dir=path
该选项用于指定 agent 储存状态的数据目录,这是所有 agent 都必须的,对于 server 尤其重要,因为他们必须持久化集群的状态。-dev
开发者模式,该选项用于创建一个开发环境下的server节点,该参数配置下,不会有任何持久化操作,即不会有任何数据写入到磁盘。dev模式仅仅是在开发和测试环境中使用,不能用于生产环境。-bootstrap-expect
该选项用于通知 consul server 类型节点,指定集群的 server 节点个数,该参数是为了延迟选举启动,直到全部的节点启动完毕以后再进行启动。-node=hostname
该 node 选项用于指定节点在集群中的名称,该名称在集群中需要是唯一的,推荐直接使用机器的 IP。-rejoin
consul 启动的时候,设置其所加入到的 consul 集群-server
以服务方式开启 consul ,允许其他的 consul 连接到开启的 consul上 (形成集群)。如果不加 -server, 表示以 “客户端” 的方式开启。不能被连接。每个数据中心(DC)的 server 数量推荐3到5个。所有的 server 节点加入到集群后要经过选举,采用 raft 一致性算法来确保数据操作的一致性。-client
该参数用于指定 consul 界定为 client 节点类型。-ui
可以使用 web 页面(进入该页面的 IP 地址与-bind
命令有关,端口与-http-port
命令有关)来查看服务发现的详情-dc
dc 是 datacenter 的简称,该选项用于指定节点加入的 dc 实例。
其它 consul 常用指令:
consul members
查看集群中的成员。consul info
查看当前 consul 的 IP 等其它信息。consul join
该命令的作用是将 agent 加入到 consul 的集群当中。当新启动一个 agent 节点后,往往需要指定节点需要加入到特定的 consul 集群中,此时使用 join 命令进行指定。consul reload
重启 consulconsul leave
优雅的关闭 consul不优雅指 ctrl + c。
使用 Consul 注册服务
运行以下命令启动
1 | consul agent -server -bootstrap-expect 1 -data-dir=d:/code/Consul/consul_data -node=n1 -bind 127.0.0.1 -ui -rejoin -config-dir=d:/code/Consul/consul.d/ -client 0.0.0.0 |
按以下步骤操作
进入配置文件路径
D:\code\Consul\consul_cfg
创建 json 文件
web.json
在该文件中,填写服务信息。
1
2
3
4
5
6
7
8
9{
"service": {
"name": "Faceid",
"tags": [
"rails"
],
"port": 9000
}
}Json 配置文件详细配置选项参见官方文档
重新启动 consul
1
consul agent -server -bootstrap-expect 1 -data-dir=d:/code/Consul/consul_data -node=n1 -bind 127.0.0.1 -ui -rejoin -config-dir=d:/code/Consul/consul.d/ -client 0.0.0.0
查询服务
浏览器查看:
直接在浏览器输入
http://127.0.0.1:8500/
,进入 consul 仪表板查看终端命令查看:
输入如下指令:
1
curl -o d:/code/Consul/test.json 127.0.0.1:8500/v1/catalog/service/Faceid
在
d:/code/Consul/test.json
文件中读取数据即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40[
{
"ID": "57bee4ce-9f8d-b68c-3fa3-5d21f98e65aa",
"Node": "n1",
"Address": "127.0.0.1",
"Datacenter": "dc1",
"TaggedAddresses": {
"lan": "127.0.0.1",
"lan_ipv4": "127.0.0.1",
"wan": "127.0.0.1",
"wan_ipv4": "127.0.0.1"
},
"NodeMeta": {
"consul-network-segment": ""
},
"ServiceKind": "",
"ServiceID": "Faceid",
"ServiceName": "Faceid",
"ServiceTags": [
"rails"
],
"ServiceAddress": "",
"ServiceWeights": {
"Passing": 1,
"Warning": 1
},
"ServiceMeta": {},
"ServicePort": 9000,
"ServiceSocketPath": "",
"ServiceEnableTagOverride": false,
"ServiceProxy": {
"Mode": "",
"MeshGateway": {},
"Expose": {}
},
"ServiceConnect": {},
"CreateIndex": 40,
"ModifyIndex": 40
}
]
Consul 健康检查
在/consul.d
路径下新建health.json
,该配置会每隔5秒检查名为Faceid
的 consul 服务在端口9000上的 http 请求是否有响应,如果超过1秒没有响应,则会判断该健康检查不通过。
1 | { |
然后通过以下命令启动 consul :
1 | consul agent -server -bootstrap-expect 1 -data-dir=d:/code/Consul/consul_data -node=n1 -bind 127.0.0.1 -ui -rejoin -config-dir=d:/code/Consul/consul.d/ -config-file d:/code/Consul/consul.d/health.json -client 0.0.0.0 |
在http://127.0.0.1:8500/
下的仪表板能看到服务健康状况:
因为我们只是在测试 consul 的功能,并没有把微服务注册上去,因此这里的HTTP API on port 9000
健康检查一定是不通过的,因为我们没有实现任何一个微服务实例能对该 consul 服务器每5秒发送一个 http 请求。同时,Faceid
服务的自检查则是能够通过 consul 健康检查的。
除了上述例子中 http 实现健康检查外,还可以使用 脚本、tcp、ttl 等方式进行健康检查。
Consul 结合 gRPC
基本 gRPC 远程调用
Protocol Buffer
GoLand 项目中新建文件夹,创建 ConsulClient.go
、 ConsulServer.go
以及文件夹 pb
。在文件夹 pb
中创建 protobuf 文件,取名为 pb.person.proto
。内容编写如下:
1 | syntax = "proto3"; |
使用语句 protoc --go-grpc_out=./ *.proto --go_out=./ *.proto
进行编译,生成两个文件:
pb.person.pb.go
pb.person_grpc.pb.go
服务端
在 ConsulServer.go
中编写如下代码
1 | package main |
注意到在新版 gRPC 实现接口的类中,除了需要 protobuf 中定义的接口,还需要匿名嵌入(更通俗的方法讲就是继承) protobuf 生成的结构体(类)
Unimplemented****Server
,其中 **** 是服务名。官方文档中对此的解释是保证程序的向前兼容( forward compatible ),即保证程序能在未来的版本迭代中保持健壮性。通俗一点讲,当版本迭代或业务需求改变,protobuf 的内容或是提供的 gRPC 服务发生改变的时候,原本该服务的 gRPC 接口实现将全部报错;但如果所有接口实现都继承了
Unimplemented****Server
类,那么程序不会报错(因为父类Unimplemented****Server
一定实现了该接口),而是会根据Unimplemented****Server
类执行一定的错误处理和报告,保证了程序的健壮性。
客户端
在 ConsulClient.go
中编写如下代码
1 | package main |
先运行服务端,再运行客户端,看到客户端输出 name:"hello Kevin" age:22
则说明运行成功。
将 gRPC 服务注册到 Consul 上
修改服务端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78package main
import (
"context"
"fmt"
"github.com/hashicorp/consul/api"
"google.golang.org/grpc"
"net"
"src/src/Consul/GRPC/pb"
)
type Children struct {
*pb.UnimplementedHelloServer // 向前兼容,即需要兼容未来的软件
}
func (c *Children) SayHello(_ context.Context, p *pb.Person) (*pb.Person, error) {
p.Name = "hello " + p.Name
return p, nil
}
func main() {
fmt.Println("hello")
// 1. 创建 Consul 对象
consulObj, err := api.NewClient(api.DefaultConfig())
if err != nil {
fmt.Println(err)
return
}
// 2. 告诉consul, 即将注册的服务的配置信息
reg := api.AgentServiceRegistration{
// 服务节点 id,用于区分提供同一服务的不同服务器
ID: "Faceid",
Tags: []string{"grpc", "consul"},
// 服务名
Name: "grpc And Consul",
Address: "127.0.0.1",
Port: 8800,
Check: &api.AgentServiceCheck{
// 该服务节点 id下,服务自健康检查的 ID
CheckID: "consul grpc test",
TCP: "127.0.0.1:8800",
Timeout: "1s",
Interval: "5s",
},
}
// 3. 把 gRPC 服务注册到 Consul 上
err = consulObj.Agent().ServiceRegister(®)
if err != nil {
return
}
// gRPC 操作
grpcServer := grpc.NewServer()
pb.RegisterHelloServer(grpcServer, new(Children))
listener, err := net.Listen("tcp", ":8800")
if err != nil {
fmt.Println(err)
return
}
defer func(listener net.Listener) {
err := listener.Close()
if err != nil {
fmt.Println(err)
}
}(listener)
fmt.Println("服务启动...")
err = grpcServer.Serve(listener)
if err != nil {
fmt.Println(err)
return
}
}修改客户端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61package main
import (
"context"
"fmt"
"github.com/hashicorp/consul/api"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"src/src/Consul/GRPC/pb"
"strconv"
)
func main() {
// 1. 创建 Consul 对象
consulObj, err := api.NewClient(api.DefaultConfig())
if err != nil {
fmt.Println(err)
return
}
// 2. 从 consul 上获取健康的服务
/*
func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error)
- 参数:
service: 服务名。 -- 注册服务时,指定该string
tag:标签名/别名。 如果有多个, 任选一个
passingOnly:是否通过健康检查。 true
q:额外查询参数。 通常传 nil
- 返回值:
ServiceEntry: 存储服务的切片。
QueryMeta:额外查询返回值。 nil
error: 错误信息
*/
services, _, err := consulObj.Health().Service("gRPC And Consul", "grpc", true, nil)
if err != nil {
fmt.Println(err)
return
}
// 可在此引入负载均衡算法
ipAddr := services[0].Service.Address + ":" + strconv.Itoa(services[0].Service.Port)
dial, err := grpc.Dial(ipAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
fmt.Println(err)
return
}
grpcClient := pb.NewHelloClient(dial)
person := pb.Person{
Name: "Kevin",
Age: 22,
}
newPerson, err := grpcClient.SayHello(context.TODO(), &person)
if err != nil {
return
}
fmt.Println(newPerson)
}通过指令
consul agent -dev
直接以 dev 模式启动 Consul ,然后先后启动服务端与客户端。在客户端看到输出:name:"hello Kevin" age:22
,并且在 Consul 控制台中看到输出:1
2022-10-15T15:19:18.385+0800 [DEBUG] agent.http: Request finished: method=GET url=/v1/health/service/gRPC%20And%20Consul?passing=1&tag=grpc from=127.0.0.1:65497 latency=2.0771ms
以上,说明我们成功将 gRPC 服务注册到 Consul 上。在 127.0.0.1:8800
的仪表板上也能看到名字为 gRPC And Consul
的服务。
服务注销
1 | package main |