你好,我是aoho,今天我们继续来介绍自己动手实现 Go 的服务注册与发现(结束)。
通过服务发现与注册中心,可以很方便地管理系统中动态变化的服务实例信息。与此同时,它也可能成为系统的瓶颈和故障点。因为服务之间的调用信息来自于服务注册与发现中心,当它不可用时,服务之间的调用可能无法正常进行。因此服务发现与注册中心一般会多实例部署,提供高可用性和高稳定性。
我们将基于 Consul 实现 Golang Web 的服务注册与发现。首先我们会通过原生态的方式,直接通过 HTTP 方式与 Consul 进行交互;然后我们会通过 Go Kit 框架提供的 Consul Client 接口实现与 Consul 之间的交互,并比较它们之间的不同。
前面两篇文章,我们了解完整个微服务结构,编写了核心的 ConsulClient 接口的实现,完成这个简单微服务和 Consul 之间服务注册与发现的流程。本文将会介绍服务下线注销和服务发现的实现。服务注册与发现组件,在各个服务实例注册到其上之后,将会向服务调用方提供所需请求调用的服务实例信息。
下面将会具体实现服务注销和服务发现的功能。
接着前面的内容,我们实现服务注销方法 DeRegister,使得服务在关闭之前主动向 Consul 发送注销请求,代码如下所示:
func (consulClient *ConsulClient) DeRegister(instanceId string, logger *log.Logger) bool {
// 1.发送注销请求
req, err := http.NewRequest("PUT",
"http://" + consulClient.Host + ":" + strconv.Itoa(consulClient.Port) + "/v1/agent/service/deregister/" +instanceId, nil)
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Println("Deregister Service Error!")
}else {
resp.Body.Close()
if resp.StatusCode == 200{
log.Println("Unregister Service Success!")
return true
}else {
log.Println("Deregister Service Error!")
}
}
return false
}
服务下线的逻辑相当简单,只需要将服务实例ID提交到 /v1/agent/service/deregister/ 路径下即可。在 main 函数中我们监控了 ctrl + c 的系统信号,在服务关闭之前会调用 closeServer 方法注销服务和关闭 web 服务。通过命令行启动服务,在 Consul 中观察到注册上去的 SayHello 服务后,我们发送 ctrl + c 组合键关闭服务,可以看到以下的命令行输出:
^C2021/07/08 21:25:40 Deregister Service Success!
2021/07/08 21:25:40 Service is going to close...
2021/07/08 21:25:40 Closed the Server!
以上输出告诉我们服务实例注销成功。回到 Consul 中,可以看到 SayHello 的服务实例确实已经不存在了,如图所示:
服务发现的关键是获取到对应服务的服务实例信息列表,然后根据一定的负载均衡策略选择具体的服务实例发起调用。DiscoverServices 方法的作用是从 Consul 中获取到对应服务的服务实例信息列表,代码如下所示:
func (consulClient *ConsulClient) DiscoverServices(serviceName string) []interface{} {
// 1. 从 Consul 中获取服务实例列表
req, err := http.NewRequest("GET",
"http://" + consulClient.Host + ":" + strconv.Itoa(consulClient.Port) + "/v1/health/service/" + serviceName, nil)
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Println("Discover Service Error!")
}else if resp.StatusCode == 200 {
var serviceList [] struct {
Service InstanceInfo `json:"Service"`
}
err = json.NewDecoder(resp.Body).Decode(&serviceList)
resp.Body.Close()
if err == nil {
instances := make([]interface{}, len(serviceList))
for i := 0; i < len(instances); i++ {
instances[i] = serviceList[i].Service
}
return instances
}
}
return nil
在 DiscoverServices 方法中,我们将服务名提交到 Consul 的 /v1/health/service/ 路径下即可获取到对应的服务实例信息列表。我们将得到的服务实例信息列表的 JSON 数据用原先定义的 InstanceInfo 结构体进行解析,就能获取到对应服务实例的 Host 和 Port 等用于服务调用的关键元数据。
在 main 函数中,我们定义了用于获取服务实例信息列表的 /discovery 端点,通过本机的 IP 和 Port 访问该接口,如笔者的 IP 为 10.93.246.254(可在 Consul 的服务页中查看),查询的服务名为 SayHello,那么请求路径为 http://10.93.246.254:10086/discovery?serviceName=SayHello,即可获取到 SayHello 服务注册到该 Consul 节点的所有服务实例信息,如下所示:
[
{
"ID": "71370f60-a76d-4ca8-9631-fb6ec49648ec",
"Service": "SayHello",
"Name": "",
"Address": "10.93.246.254",
"Port": 10086,
"EnableTagOverride": false,
"Check": {
"DeregisterCriticalServiceAfter": "",
"HTTP": ""
},
"Weights": {
"Passing": 10,
"Warning": 1
}
}
]
本文主要实现了微服务实例与 Consul 交互过程,包括服务实例的注销、服务发现。
通过三篇文章,我介绍了基于 Consul 自定义实现 Go 的服务注册与发现。这部分代码的实现,你可以封装成包进行调用,加入自己自定义的功能和用法。
完整代码,从我的Github获取,https://github.com/longjoy/micro-go-book