前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自己动手实现 Go 的服务注册与发现(中)

自己动手实现 Go 的服务注册与发现(中)

作者头像
aoho求索
发布2021-12-15 16:37:28
1.1K0
发布2021-12-15 16:37:28
举报
文章被收录于专栏:aoho求索

你好,我是aoho,今天继续和大家分享动手实现 Go 的服务注册与发现!

通过服务发现与注册中心,可以很方便地管理系统中动态变化的服务实例信息。与此同时,它也可能成为系统的瓶颈和故障点。因为服务之间的调用信息来自于服务注册与发现中心,当它不可用时,服务之间的调用可能无法正常进行。因此服务发现与注册中心一般会多实例部署,提供高可用性和高稳定性。

我们将基于 Consul 实现 Golang Web 的服务注册与发现。首先我们会通过原生态的方式,直接通过 HTTP 方式与 Consul 进行交互;然后我们会通过 Go Kit 框架提供的 Consul Client 接口实现与 Consul 之间的交互,并比较它们之间的不同。

前面一篇文章,我们了解完整个微服务结构,我们将开始编写核心的 ConsulClient 接口的实现,完成这个简单微服务和 Consul 之间服务注册与发现的流程。

服务实例与 Consul 交互

在这一部分中,我们会直接通过 HTTP 的方式与 Consul 完成交互,完成服务注册和服务发现的功能。我们首先定义服务注册时的服务实例结构体 diy.InstanceInfo,源码位于 ch7-discovery/diy/MyConsulClient.go。代码如下所示:

代码语言:javascript
复制
// 服务实例结构体
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 的服务实例信息主要包含:

  • 服务实例ID,用于唯一标记服务实例
  • 服务名,服务实例所属的服务集群
  • Address、Port,服务地址和端口,用于发起服务间调用
  • Check,健康检查信息,包括健康检查地址,健康检查的间隔等。

Consul 中支持由 Consul 主动调用服务实例提供的健康检查接口以维持心跳,和由服务实例主动提交健康检查数据到 Consul 中维持心跳。Check 中的 Interval 和 TTL 的参数分别用于设置两者的检查间隔时长,只能设置其中之一。我们的微服务采用主动检查的方式,提供 /health 接口由 Consul 调用检查。

接着我们定义 diy.ConsulClint 的结构体和它的创建函数,它们位于 ch7-discovery/diy/MyConsulClient.go 下,代码如下所示:

代码语言:javascript
复制
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,空方法代码如下:

代码语言:javascript
复制
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 下,代码如下所示:

代码语言:javascript
复制
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 方法中主要执行了以下操作:

  1. 将服务实例数据封装为 InstanceInfo,这其中我们设定了服务实例ID、服务名、服务地址、服务端口等关键数据,并指定了健康检查的地址为 /health,检查时间间隔为 15s。DeregisterCriticalServiceAfter 参数定义了如果 30s 内健康检查失败,该服务实例将被 Consul 主动下线。
  2. 通过 HTTP 的方式向 Consul 发起注册请求,将上一步的封装好的 InstanceInfo 提交到注册表中,服务注册的地址为 /v1/agent/service/register。

在 main 函数中,我们定义了服务启动时会首先调用 ConsulClient#Register 发起服务注册。可以通过在 ch7-discovery 目录下启动该微服务以验证服务注册和健康检查的效果,启动命令如下:

代码语言:javascript
复制
 go run main/SayHelloService.go

可以看到命令行中打出了对应的启动和健康检查日志:

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 aoho求索 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 服务实例与 Consul 交互
  • 服务注册与健康检查
  • 小结
相关产品与服务
微服务引擎 TSE
微服务引擎(Tencent Cloud Service Engine)提供开箱即用的云上全场景微服务解决方案。支持开源增强的云原生注册配置中心(Zookeeper、Nacos 和 Apollo),北极星网格(腾讯自研并开源的 PolarisMesh)、云原生 API 网关(Kong)以及微服务应用托管的弹性微服务平台。微服务引擎完全兼容开源版本的使用方式,在功能、可用性和可运维性等多个方面进行增强。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档