内容简介: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底层分为6个组件,分别是broker、Codec、Register、Selector、Transport。 Registry是go-micro的注册模块,它提供可插拔的服务注册与发现功能,它目前的实现的方式有Consul,etcd,内存和k8s。我们以consul为例子,来看一下go-micro是如何完成整个注册实现的。
准备工作
- 需要consul,你可以在consul官网上下载consul的二进制可执行文件,可以直接使用命令
./consul agent -dev -client 0.0.0.0 -ui来启用consul - 参考go-micro doc里的greeter例子。设置
MICRO_REGISTRY=consul环境变量,然后跑通demo。 - 我使用的是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
参考包的接口,我们可以知道注册器支持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.Broker 、 s.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的服务注册流程,选择注册器,然后进行服务的注册,其他的注册器的逻辑也是一样的就不再复述。
下面是我记录的代码流程图
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 【Java集合源码剖析】ArrayList源码剖析
- Java集合源码剖析:TreeMap源码剖析
- 我的源码阅读之路:redux源码剖析
- ThreadLocal源码深度剖析
- SharedPreferences源码剖析
- Volley源码剖析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
我的第一本编程书
[日]平山尚 / 张沈宇 / 人民邮电出版社 / 2016-7 / 79.00元
写这本书之前,作者一直在摸索一种最有利于入门者学编程的方法,并应用到教学当中。经过两年的教学实践,他确信他的方法是有效的,于是便有了这本书。这本书面向的是完全没有接触过编程的读者。作者将门槛设置得非常低,读者不需要懂得变量、函数这些名词(这些名词在书中也不会出现),不需要会英语,完全不需要查阅其他书籍,只需要小学算术水平即可。这本书给初学者非常平缓的学习曲线,有利于为之后的进阶学习打下坚实的基础。一起来看看 《我的第一本编程书》 这本书的介绍吧!
MD5 加密
MD5 加密工具
RGB CMYK 转换工具
RGB CMYK 互转工具