go-micro之源码剖析: Registry

栏目: Go · 发布时间: 6年前

内容简介:go-micro的结构图如下(来源git仓库)。可以看到go-micro底层分为6个组件,分别是broker、Codec、Register、Selector、Transport。 Registry是go-micro的注册模块,它提供可插拔的服务注册与发现功能,它目前的实现的方式有Consul,etcd,内存和k8s。我们以consul为例子,来看一下go-micro是如何完成整个注册实现的。

go-micro 提供了分布式系统开发的核心需求,包括RPC和事件驱动交换。关于go-micro的详细内容请参考git上的go-micro项目,这篇文章主要来讲go-micro的组件register的源码剖析。

go-micro的结构图如下(来源git仓库)。

图1.1

go-micro之源码剖析: Registry

可以看到go-micro底层分为6个组件,分别是broker、Codec、Register、Selector、Transport。 Registry是go-micro的注册模块,它提供可插拔的服务注册与发现功能,它目前的实现的方式有Consul,etcd,内存和k8s。我们以consul为例子,来看一下go-micro是如何完成整个注册实现的。

准备工作

  1. 需要consul,你可以在consul官网上下载consul的二进制可执行文件,可以直接使用命令 ./consul agent -dev -client 0.0.0.0 -ui 来启用consul
  2. 参考go-micro doc里的greeter例子。设置 MICRO_REGISTRY=consul 环境变量,然后跑通demo。
  3. 我使用的是mac版本的goland,可以方便源代码跟踪,你可以使用goland或者是 delve 类似的 工具 进行debug。

代码剖析

服务注册在go-micro服务端实现,找到service demo里的 service.Run() ,它是最外层service启动的入口。 Run 的实现在service.go里。进入到Run方法里里找到 (s *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() 。跟踪代码进入这个start函数,进入到 (s *rpcServer) Start() 里面,这里就是go-micro里service的核心代码。代码行数比较多,我们直接关注重点也就是register的功能。找到 Register 注册部分的的代码。

func (s *rpcServer) Start() error {
    ...
    // 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.Log("Server %s-%s register error: %s", config.Name, config.Id, err)
		}
	}
	...
}
复制代码

这里,首先检查了register环境的上下文。如果没有问题则进行注册操作。进入到 s.Register() 里面, (s *rpcServer) Register() 这个函数就是注册功能的核心代码部分。不看前面的预处理,只看我们关注的核心部分,找到下面的代码行:

func (s *rpcServer) Register() error {
    ...
    if err := config.Registry.Register(service, rOpts...); err != nil {
    	return err
    }
    ...
}    
复制代码

不难猜出,这里就是注册的功能实现了,但是go-micro是怎么知道该使用哪个注册器呢。

先看一下registry包的结构。

图1.2

go-micro之源码剖析: Registry

参考包的接口,我们可以知道注册器支持4种类型的注册操作,分别是consul、gossip、mdns、memory。 我们设置了 MICRO_REGISTRY=consul 的环境变量,来告诉go-micro使用consul方式注册。那么, config.Registry 到底是在哪设置成consulRegister的呢。

答案是,在 service.Init() 服务初始化的里面进行了设置。回到service demo里

// Init will parse the command line flags.
service.Init()
复制代码

跟踪进入Init函数

func (s *service) Init(opts ...Option) {
	// process options
	for _, o := range opts {
		o(&s.opts)
	}

	s.once.Do(func() {
		// Initialise the command flags, overriding new service
		_ = s.opts.Cmd.Init(
			cmd.Broker(&s.opts.Broker),
			cmd.Registry(&s.opts.Registry),
			cmd.Transport(&s.opts.Transport),
			cmd.Client(&s.opts.Client),
			cmd.Server(&s.opts.Server),
		)
	})
}
复制代码

Init里面,for循环执行了一系列预处理的函数。然后使用了sync.Once里的once操作,保证里面的函数只执行一次。重点关注 Cmd.Init 这个方法,它这里接收的参数使用的比较绕。

func (c *cmd) Init(opts ...Option) error {
	for _, o := range opts {
		o(&c.opts)
	}
	c.app.Name = c.opts.Name
	c.app.Version = c.opts.Version
	c.app.HideVersion = len(c.opts.Version) == 0
	c.app.Usage = c.opts.Description
	c.app.RunAndExitOnError()
	return nil
}
复制代码

首先 cmd.Init 的参数接受 type Option func(o *Options) 类型的方法,然后依次执行,然后再进行变量赋值和函数处理。

它接受的方法以broker为例。

func Broker(b *broker.Broker) Option {
	return func(o *Options) {
		o.Broker = b
	}
}
复制代码

Broker方法有点绕,要和前面两个Init一起看。Broker方法接受了一个 Broker 类型的指针。它返回了一个 Option 方法, Option 方法接受一个 Options 类型的指针。然后把 o *Options 的Broker设置成外层函数参数传进来的Broker。

进入到 cmd.Init 里for循环依次执行了同Broker类似的方法, o(&c.opts) 也就是把c.opts对应的组件进行赋值。所赋的值就是 service.Init 里的 s.opts.Brokers.opts.Registry 等组件。

所以它一些列的操作就是s.opts.Cmd.opts的组件去复用service.opts上的组件。这里可以学习一下它这种写法,在对象不可见的情况下传递对象的字段。(这种写法其他的好处,请大佬一定诉我)

我们继续找,进入 c.app.RunAndExitOnError() 的这个方法里,然后进入到 a.Run()

func (a *App) Run(arguments []string) (err error) {
    ...
    if a.Before != nil {
		err = a.Before(context)
		if err != nil {
			fmt.Fprintf(a.Writer, "%v\n\n", err)
			ShowAppHelp(context)
			return err
		}
	}
	...
}
复制代码

直接告诉你设置Register的位置是在a.Before这里。这个方法没有自己定义,也是复用了cmd.Before方法。cmd.go的 newCmd(opts ...Option) 方法里你会找到 cmd.app.Before = cmd.Before ,就是在这里设置的。最终赋值的地方就是在这个 cmd.Before 里。

func (c *cmd) Before(ctx *cli.Context) error {
    ...
    if name := ctx.String("registry"); len(name) > 0 && (*c.opts.Registry).String() != name {
		r, ok := c.opts.Registries[name]
		if !ok {
			return fmt.Errorf("Registry %s not found", name)
		}

		*c.opts.Registry = r()
		serverOpts = append(serverOpts, server.Registry(*c.opts.Registry))
		clientOpts = append(clientOpts, client.Registry(*c.opts.Registry))
		...
	}	
    ...
}
复制代码

那么到这里我们就知道了,它会去从环境变量里拿registry的值,我们设置的是consul,对应的就是consulRegistry。然后 *c.opts.Registry = r() 这里最终设置了这个注册器。

费劲千辛万苦终于知道从哪里拿的Registry。接下来回到上面的 Register 函数调用那里。我们去找consulRegistry的 Register 函数

func (c *consulRegistry) Register(s *registry.Service, opts ...registry.RegisterOption) error {
    ...
    if err := c.Client.Agent().ServiceRegister(asr); err != nil {
		return err
	}
	...
}
复制代码

这个函数比较长,它主要做的处理是和consul服务进行通信。在 Agent().ServiceRegister(asr) 里面,它发起了一个PUT请求,具体的请求内容可以自己去实际看一下发包。

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
}
复制代码

到这里,go-micro就讲服务注册到了consul上。最终consul会连接你的服务端和客户端,让它们之间进行通信。

到此我们从源码角度,了解了go-micro的服务注册流程,选择注册器,然后进行服务的注册,其他的注册器的逻辑也是一样的就不再复述。

下面是我记录的代码流程图


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Responsive Web Design

Responsive Web Design

Ethan Marcotte / Happy Cog / 2011-6 / USD 18.00

From mobile browsers to netbooks and tablets, users are visiting your sites from an increasing array of devices and browsers. Are your designs ready? Learn how to think beyond the desktop and craft be......一起来看看 《Responsive Web Design》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试