自己动手实现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 的服务实例确实已经不存在了,如图所示:

Consul UI.png

服务发现

服务发现的关键是获取到对应服务的服务实例信息列表,然后根据一定的负载均衡策略选择具体的服务实例发起调用。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获取,github.com/longjoy/mic…

往期推荐

  1. # 自己动手实现 Go 的服务注册与发现(中)
  2. 如何学习 etcd?|我的新书出版啦

阅读最新文章,关注公众号:aoho求索