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

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

作者头像
aoho求索
发布2021-12-04 13:00:58
1.1K0
发布2021-12-04 13:00:58
举报
文章被收录于专栏:aoho求索

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

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

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

Consul 的安装与启动

在此之前,我们首先需要搭建一个简单的 Consul 服务,Consul 的下载地址为 https://www.consul.io/downloads.html,根据操作系统的不同进行下载。在 Unix 环境下(Mac、Linux),下载下来的文件是一个二进制可执行文件,可以直接通过它执行 Consul 的相关命令。Window 环境下是一个 .exe 的可执行文件。

以笔者自身的 Linux 环境为例,直接在 consul 文件所在的目录执行:

代码语言:javascript
复制
./consul version

能够直接获取到刚才下载的 consul 的版本:

代码语言:javascript
复制
Consul v1.5.1
Protocol 2 spoken by default,
understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)

如果我们想要将 consul 归于系统命令下,可以使用以下命令将 consul 移动到 /usr/local/bin 文件下:

代码语言:javascript
复制
sudo mv consul /usr/local/bin/

接着我们通过以下命令启动 Consul:

代码语言:javascript
复制
consul agent -dev

-dev 选项说明 Consul 以开发模式启动,该模式下会快速部署一个单节点的 Consul 服务,部署好的节点既是 Server 也是 Leader。在生产环境不建议以这种模式启动,因为它不会持久化任何数据,数据仅存在于内存中。

启动好之后就可以在浏览器访问 http://localhost:8500 地址,如图所示:

Consul UI.png

服务注册与发现接口

为了减少代码的重复度,我们首先定义一个 Consul 客户端接口,源码位于 ch7-discovery/ConsulClient.go 下,代码如下所示,

代码语言:javascript
复制
type ConsulClient interface {

 /**
  * 服务注册接口
  * @param serviceName 服务名
  * @param instanceId 服务实例Id
  * @param instancePort 服务实例端口
  * @param healthCheckUrl 健康检查地址
  * @param meta 服务实例元数据
  */
 Register(serviceName, instanceId, healthCheckUrl string, instancePort int, meta map[string]string, logger *log.Logger) bool

 /**
  * 服务注销接口
  * @param instanceId 服务实例Id
  */
 DeRegister(instanceId string, logger *log.Logger) bool

 /**
  * 服务发现接口
  * @param serviceName 服务名
  */
 DiscoverServices(serviceName string) []interface{}
}

代码中提供了三个接口,分别是:

  • Register,用于服务注册,服务实例将自身所属服务名和服务元数据注册到 Consul 中;
  • DeRegister,用于服务注销,服务关闭时请求 Consul 将自身元数据注销,避免无效请求;
  • DiscoverServices,用于服务发现,通过服务名向 Consul 请求对应的服务实例信息列表。

接着我们定义一个简单的服务 main 函数,它将启动 Web 服务器,使用 ConsulClient 将自身服务实例元数据注册到 Consul,提供一个 /health 端点用于健康检查,并在服务下线时从 Consul 注销自身。源码位于 ch7-discovery/main/SayHelloService.go 中,代码如下所示:

代码语言:javascript
复制
var consulClient ch7_discovery.ConsulClient
var logger *log.Logger

func main()  {

 // 1.实例化一个 Consul 客户端,此处实例化了原生态实现版本
 consulClient = diy.New("127.0.0.1", 8500)
 // 实例失败,停止服务
 if consulClient == nil{
  panic(0)
 }

 // 通过 go.uuid 获取一个服务实例ID
 instanceId := uuid.NewV4().String()
 logger = log.New(os.Stderr, "", log.LstdFlags)
 // 服务注册
 if !consulClient.Register("SayHello", instanceId, "/health", 10086, nil, logger) {
  // 注册失败,服务启动失败
  panic(0)
 }

 // 2.建立一个通道监控系统信号
 exit := make(chan os.Signal)
 // 仅监控 ctrl + c
 signal.Notify(exit, syscall.SIGINT, syscall.SIGTERM)
 var waitGroup sync.WaitGroup
 // 注册关闭事件,等待 ctrl + c 系统信号通知服务关闭
 go closeServer(&waitGroup, exit, instanceId, logger)

 // 3. 在主线程启动http服务器
 startHttpListener(10086)

 // 等待关闭事件执行结束,结束主线程
 waitGroup.Wait()
 log.Println("Closed the Server!")

}

在这个简单的微服务 main 函数中,主要进行了以下的工作:

  1. 实例化 ConsulClient,调用 Register 方法完成服务注册。注册的服务名为SayHello,服务实例 ID 由 UUID 生成,健康检查地址为 /health,服务实例端口为 10086;
  2. 注册关闭事件,监控服务关闭事件。在服务关闭时调用 closeServer 方法进行服务注销和关闭 http 服务器;
  3. 启动 http 服务器。

在服务关闭之前,我们会调用 ConsulClient#Deregister 方法,将服务实例从 Consul 中注销,代码位于 closeServer 方法中,如下所示:

代码语言:javascript
复制
func closeServer( waitGroup *sync.WaitGroup, exit <-chan os.Signal, instanceId string, logger *log.Logger)  {
 // 等待关闭信息通知
 <- exit
 // 主线程等待
 waitGroup.Add(1)
 // 服务注销
 consulClient.DeRegister(instanceId, logger)
 // 关闭 http 服务器
 err := server.Shutdown(nil)
 if err != nil{
  log.Println(err)
 }
 // 主线程可继续执行
 waitGroup.Done()
}

closeServer 方法除了进行服务注销,还会将本地服务的 http 服务关闭。在 startHttpListener 方法中,我们注册了三个 http 接口,分别为 /health 用于 Consul 的健康检查,/sayHello 用于检查服务是否可用,以及 /discovery 用于将从 Consul 中发现的服务实例信息打印出来,代码如下所示:

代码语言:javascript
复制
func startHttpListener(port int)  {
 server = &http.Server{
  Addr: ch7_discovery.GetLocalIpAddress() + ":" +strconv.Itoa(port),
 }
 http.HandleFunc("/health", CheckHealth)
 http.HandleFunc("/sayHello", sayHello)
 http.HandleFunc("/discovery", discoveryService)
 err := server.ListenAndServe()
 if err != nil{
  logger.Println("Service is going to close...")
 }
}

checkHealth 用于处理来自 Consul 的健康检查,我们这里仅是直接简单返回,实际使用时可以检测实例的性能和负载情况,返回有效的健康检查信息。代码如下所示:

代码语言:javascript
复制
func CheckHealth(writer http.ResponseWriter, reader *http.Request) c{
 logger.Println("Health check starts!")
 _, err := fmt.Fprintln(writer, "Server is OK!")
 if err != nil{
  logger.Println(err)
 }
}

discoveryService 从请求参数中获取 serviceName,并调用 ConsulClient#DiscoverServices 方法从 Consul 中发现对应服务的服务实例列表,然后将结果返回到 response 中。代码如下所示:

代码语言:javascript
复制
func discoveryService(writer http.ResponseWriter, reader *http.Request)  {
 serviceName := reader.URL.Query().Get("serviceName")
 instances := consulClient.DiscoverServices(serviceName)
 writer.Header().Set("Content-Type", "application/json")
 err := json.NewEncoder(writer).Encode(instances)
 if err != nil{
  logger.Println(err)
 }
}

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

小结

仅有服务注册与发现中心是不够,还需要各个服务实例的鼎力配合,整个服务注册与发现体系才能良好运作。一个服务实例需要完成以下的事情:

  • 在服务启动阶段,提交自身服务实例元数据到服务发现与注册中心,完成服务注册;
  • 服务运行阶段,定期和服务注册与发现中心维持心跳,保证自身在线状态。如果可能,还会检测自身元数据的变化,在服务实例信息发生变化时重新提交数据到服务注册与发现中心;
  • 在服务关闭时,向服务注册与发现中心发出下线请求,注销自身在注册表中的服务实例元数据。

下面的文章将会继续实现微服务与 Consul 的注册与服务查询等交互。

完整代码,从我的Github获取,https://github.com/longjoy/micro-go-book

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

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

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

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

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