Go Micro 底层是如何将服务注册到 Consul 的


consul-arch-single.png

前面两篇分享学院君已经简单介绍了基于 Go Micro 框架实现服务接口的发布和调用,接下来,我们以 Consul 作为注册中心为例,介绍 Go Micro 框架底层是如何将服务注册到 Consul 的。

服务注册与发现流程

服务的注册和发现是微服务架构中必不可少的重要一环,Go Micro 为此提供了专门的接口 Registry 来处理,实现该接口的组件即可作为微服务的注册中心用于服务注册与发现,在我们的示例中以 Consul 作为注册中心,所有微服务服务端启动时,会将服务信息注册到 Consul,然后客户端调用服务时,会从 Consul 中查询服务接口信息,再发起请求,对应流程图如下:

Consul

以前面介绍的 GreeterService 为例:

  1. GreeterService 服务端启动时,会向 Consul 发送一个 POST 请求,告诉 Consul 自己的 IP 和端口;
  2. Consul 接收到 GreeterService 的注册请求后,每隔 10s(默认)会向 GreeterService 发送一个健康检查的请求,检验 GreterService 是否有效(心跳检测);
  3. 当客户端以 HTTP 接口方式发送 GET 请求 /greeter/say/helloGreeterService 时,会先从 Consul 中拿到一个存储对应服务 IP 和端口的临时表,并从表中查询 GreeterService 的 IP 和端口,再发送 GET 方式请求到 /greeter/say/hello。该临时表每隔 10s 会更新,只包含有通过了健康检查的 Service。此外,为了提高性能和系统可用性,往往会缓存服务信息到本地,如果服务部署在多个机器,还会使用负载均衡算法选择指定服务端通信。

服务注册底层实现细节

我们先来看看服务端启动时是如何将服务信息注册到 Consul 的,打开 ~/go/hello/src/hello/main.go 这个服务端主入口文件,可以在 main 函数末尾看到这段代码:

// Run the server
if err := service.Run(); err != nil {
    fmt.Println(err)
}

service.Run() 是服务启动的入口函数,对应的源码位于 go-micro/service.go

func (s *service) Run() error {
	if err := s.Start(); err != nil {
		return err
	}

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)

	select {
	// wait on kill signal
	case <-ch:
	// wait on context cancel
	case <-s.opts.Context.Done():
	}

	return s.Stop()
}

这里面会调用 service.Start() 方法:

func (s *service) Start() error {
	for _, fn := range s.opts.BeforeStart {
		if err := fn(); err != nil {
			return err
		}
	}

	if err := s.opts.Server.Start(); err != nil {
		return err
	}

	for _, fn := range s.opts.AfterStart {
		if err := fn(); err != nil {
			return err
		}
	}

	return nil
}

该方法会依次执行服务启动前逻辑、启动逻辑和启动后逻辑,我们重点关注服务启动过程中做了啥,追踪 s.opts.Server.Start() 方法,这段源码位于 go-micro/server/rpc_server.go 文件中的 (s *rpcServer) Start() 函数,该函数中的代码就是咱们服务启动的核心逻辑所在了,由于涉及到的代码量比较大,我们重点关注服务注册部分:

// use RegisterCheck func before register
if err = s.opts.RegisterCheck(s.opts.Context); err != nil {
    log.Logf("Server %s-%s register check error: %s", config.Name, config.Id, err)
} else {
    // announce self to the world
    if err = s.Register(); err != nil {
        log.Logf("Server %s-%s register error: %s", config.Name, config.Id, err)
    }
}

这里先对上下文环境做检测,没有问题才正式开始服务注册,对应的注册逻辑位于当前文件下的 (s *rpcServer) Register() 方法,在该方法中,会获取服务所在机器的 IP、端口以及服务名称、节点ID、元数据、处理器列表、订阅者等信息并将其注册到系统默认的注册中心:

if err := config.Registry.Register(service, rOpts...); err != nil {
    return err
}

这里我们通过系统环境变量 MICRO_REGISTRY=consul 指定默认注册中心为 Consul,所以我们直接去 go-micro/registry/consul/consul.go 中看 (c *consulRegistry) Register 方法的实现,这个方法代码量也比较大,前面一大堆逻辑都是做环境检测和变量的初始化,我们直接拉到后面的服务注册部分:

// register the service
// 待注册服务信息
  asr := &consul.AgentServiceRegistration{
      ID:      node.Id,
      Name:    s.Name,
      Tags:    tags,
      Port:    node.Port,
      Address: node.Address,
      Check:   check,
  }
  
  // Specify consul connect
  if c.connect {
      asr.Connect = &consul.AgentServiceConnect{
         Native: true,
      }
   }

 // 注册服务信息到 Consul
  if err := c.Client.Agent().ServiceRegister(asr); err != nil {
	   return err
  }

具体注册逻辑在 c.Client.Agent().ServiceRegister 对应的方法中

func (a *Agent) ServiceRegister(service *AgentServiceRegistration) error {
	r := a.c.newRequest("PUT", "/v1/agent/service/register")
	r.obj = service
	_, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}

系统会发起一个 PUT 请求到 Consul 的服务注册接口 /v1/agent/service/register,并将服务注册信息作为请求实体传递过去,请求失败则返回错误信息,请求成功,则可以在 Consul 上看到注册的新服务。

以上,就是 Go Micro 框架服务注册的底层实现细节,基于其它组件的注册中心总体流程也是如此,只是不同注册中心实现服务注册的细节有所差异罢了,下一篇,学院君将给大家介绍客户端发现服务、调用服务的底层实现细节。


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 通过 HTTP 请求调用 Go Micro 提供的微服务接口

>> 下一篇: 基于 Consul 的 Go Micro 客户端服务发现是如何实现的