你好,我是aoho,今天继续和大家分享动手实现 Go 的服务注册与发现!
通过服务发现与注册中心,可以很方便地管理系统中动态变化的服务实例信息。与此同时,它也可能成为系统的瓶颈和故障点。因为服务之间的调用信息来自于服务注册与发现中心,当它不可用时,服务之间的调用可能无法正常进行。因此服务发现与注册中心一般会多实例部署,提供高可用性和高稳定性。
我们将基于 Consul 实现 Golang Web 的服务注册与发现。首先我们会通过原生态的方式,直接通过 HTTP 方式与 Consul 进行交互;然后我们会通过 Go Kit 框架提供的 Consul Client 接口实现与 Consul 之间的交互,并比较它们之间的不同。
前面一篇文章,我们了解完整个微服务结构,我们将开始编写核心的 ConsulClient 接口的实现,完成这个简单微服务和 Consul 之间服务注册与发现的流程。
在这一部分中,我们会直接通过 HTTP 的方式与 Consul 完成交互,完成服务注册和服务发现的功能。我们首先定义服务注册时的服务实例结构体 diy.InstanceInfo,源码位于 ch7-discovery/diy/MyConsulClient.go。代码如下所示:
// 服务实例结构体
type InstanceInfo struct {
ID string `json:"ID"` // 服务实例ID
Name string `json:"Name"` // 服务名
Service string `json:"Service,omitempty"` // 服务发现时返回的服务名
Tags []string `json:"Tags,omitempty"` // 标签,可用于进行服务过滤
Address string `json:"Address"` // 服务实例HOST
Port int `json:"Port"` // 服务实例端口
Meta map[string]string `json:"Meta,omitempty"` // 元数据
EnableTagOverride bool `json:"EnableTagOverride"` // 是否允许标签覆盖
Check `json:"Check,omitempty"` // 健康检查相关配置
Weights `json:"Weights,omitempty"` // 权重
}
type Check struct {
DeregisterCriticalServiceAfter string `json:"DeregisterCriticalServiceAfter"` // 多久之后注销服务
Args []string `json:"Args,omitempty"` // 请求参数
HTTP string `json:"HTTP"` // 健康检查地址
Interval string `json:"Interval,omitempty"` // Consul 主动进行健康检查
TTL string `json:"TTL,omitempty"` // 服务实例主动提交健康检查,与Interval只存其一
}
type Weights struct {
Passing int `json:"Passing"`
Warning int `json:"Warning"`
}
提交到 Consul 的服务实例信息主要包含:
Consul 中支持由 Consul 主动调用服务实例提供的健康检查接口以维持心跳,和由服务实例主动提交健康检查数据到 Consul 中维持心跳。Check 中的 Interval 和 TTL 的参数分别用于设置两者的检查间隔时长,只能设置其中之一。我们的微服务采用主动检查的方式,提供 /health 接口由 Consul 调用检查。
接着我们定义 diy.ConsulClint 的结构体和它的创建函数,它们位于 ch7-discovery/diy/MyConsulClient.go 下,代码如下所示:
type ConsulClient struct {
Host string // Consul 的 Host
Port int // Consul 的 端口
}
func New(consulHost string, consulPort int) *ConsulClient {
return &ConsulClient{
Host: consulHost,
Port: consulPort,
}
}
获取一个 diy.ConsulClient 需要传递 Consul 的具体地址,即 Consul 的 Host 和 Port。
之后我们在 ch7-discovery/diy/MyConsulClient.go 下实现 ch7-discovery/ConsulClient 接口,并指定方法的接收器为 diy.ConsulClient,空方法代码如下:
func (consulClient *ConsulClient) Register(serviceName, instanceId, healthCheckUrl string, instancePort int, meta map[string]string, logger *log.Logger) bool{
return false
}
func (consulClient *ConsulClient) DeRegister(instanceId string, logger *log.Logger) bool {
return false
}
func (consulClient *ConsulClient) DiscoverServices(serviceName string) []interface{}{
return nil
}
我们首先实现服务注册的功能,即实现 Register 接口,指定方法的接收器为 diy.ConsulClient。源码位于 ch7-discovery/diy/MyConsulClient.go 下,代码如下所示:
func (consulClient *ConsulClient) Register(serviceName, instanceId, healthCheckUrl string, instancePort int, meta map[string]string, logger *log.Logger) bool{
// 获取服务的本地IP
instanceHost := ch7_discovery.GetLocalIpAddress()
// 1.封装服务实例的元数据
instanceInfo := &InstanceInfo{
ID: instanceId,
Name: serviceName,
Address: instanceHost,
Port: instancePort,
Meta: meta,
EnableTagOverride: false,
Check: Check{
DeregisterCriticalServiceAfter: "30s",
HTTP: "http://" + instanceHost + ":" + strconv.Itoa(instancePort) + healthCheckUrl,
Interval: "15s",
},
Weights: Weights{
Passing: 10,
Warning: 1,
},
}
byteData,_ := json.Marshal(instanceInfo)
// 2. 向 Consul 发送服务注册的请求
req, err := http.NewRequest("PUT",
"http://" + consulClient.Host + ":" + strconv.Itoa(consulClient.Port) + "/v1/agent/service/register",
bytes.NewReader(byteData))
if err == nil {
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
client := http.Client{}
resp, err := client.Do(req)
// 3. 检查注册结果
if err != nil {
log.Println("Register Service Error!")
} else {
resp.Body.Close()
if resp.StatusCode == 200 {
log.Println("Register Service Success!")
return true;
} else {
log.Println("Register Service Error!")
}
}
}
return false
}
Register 方法中主要执行了以下操作:
在 main 函数中,我们定义了服务启动时会首先调用 ConsulClient#Register 发起服务注册。可以通过在 ch7-discovery 目录下启动该微服务以验证服务注册和健康检查的效果,启动命令如下:
go run main/SayHelloService.go
可以看到命令行中打出了对应的启动和健康检查日志:
2019/07/08 20:45:21 Register Service Success!
2019/07/08 20:45:22 Health check starts!
访问 Consul 的主页面 http://localhost:8500,可以看到 SayHello 服务已经注册到 Consul 中,如图所示:
Register1.png
直接点击页面中的 SayHello 服务,能够进入到服务集群页面,查看该集群下的服务实例信息,如图所示:
Register2.png
上图中显示了我们注册上去的服务实例ID、地址和端口等关键信息。
本文主要实现了微服务实例与 Consul 交互过程,以及服务注册与健康检查的实现。那么服务注册之后如何注销,以及如何让其他服务发现呢?
下面的文章将会继续实现服务注销与服务发现的功能。
完整代码,从我的Github获取,https://github.com/longjoy/micro-go-book