Go-micro provides the core requirements for distributed system development, including RPC and event-driven communication mechanisms. For details about go-Micro, please refer to the Go-Micro project on Git. This article is mainly about the source code analysis of the Go-Micro component Register.

The go-Micro structure is shown below (source git repository).

Figure 1.1

As you can see, the go-Micro layer is divided into six components, namely broker, Codec, Register, Selector and Transport. Registry is a Registry module for Go-Micro that provides pluggable service registration and discovery capabilities and is currently implemented in Consul, ETCD, Memory and K8S. Using Consul as an example, let’s take a look at how Go-Micro completes the entire registration implementation.

The preparatory work

  1. You need Consul, you can go to ConsulwebsiteDownload consul’s binary executable file from the/consul agent-dev-client 0.0.0.0-uiTo enable the consul
  2. See Greeter in Go-Micro Doc for an example. Set up theMICRO_REGISTRY=consulEnvironment variables, and then run through demo.
  3. I’m using the MAC version of Goland, which makes it easy to track source code. You can debug using Goland or something like Delve.

Code analysis

The service registration is implemented on the Go-Micro server. Find service.run () in the Service Demo, which is the entry point for starting the outermost service. The implementation of Run is in service.go. Go inside the Run method and find (s *service) Start().

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

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

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

	return nil
}
Copy the code

Internally, this method is executed sequentially, with server pre-start event processing, service start, and service end event processing. The core code is in s.pts. Server.start (). The trace code goes into the start function, inside (s *rpcServer) start (), which is the core code of the Service in Go-Micro. The number of lines of code is quite large, so let’s focus directly on the function of register. Find the code for the Register section.

func (s *rpcServer) Start() error {
    ...
    // use RegisterCheck func before register
	iferr = 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
		iferr = s.Register(); err ! = nil { log.Log("Server %s-%s register error: %s", config.Name, config.Id, err)
		}
	}
	...
}
Copy the code

Here, you first check the context of the Register environment. If there are no problems, the registration operation is performed. Enter s.register (), (s *rpcServer) Register() this function is the core code part of the registration function. Instead of preprocessing, just look at the core of our focus and find the following line of code:

func (s *rpcServer) Register() error {
    ...
    iferr := config.Registry.Register(service, rOpts...) ; err ! = nil {return err
    }
    ...
}    
Copy the code

It’s not hard to guess that this is where the registration function is implemented, but how does Go-Micro know which registrar to use?

Take a look at the structure of the Registry package.

Figure 1.2

According to the directory structure of the package, there are four types of registry operations supported by the registry: Consul, Gossip, MDNS and Memory. We set the environment variable MICRO_REGISTRY=consul to tell Go-Micro to register in Consul mode. So, where exactly is config.Registry set up to consult register?

The answer is that it is set in the Init() service. Back to service Demo

// Init will parse the command line flags.
service.Init()
Copy the code

Trace into the Init function

func (s *service) Init(opts ... Option) { // process optionsfor _, 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),
		)
	})
}
Copy the code

Init, the for loop executes a bunch of preprocessed functions. We then use the Once operation in sync.once to ensure that the function is executed only Once. Focus on the cmd. Init method, which uses a comparison loop for the parameters it receives.

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
}
Copy the code

Cmd.init takes a method of type Type Option func(O *Options) and executes it in sequence, followed by variable assignment and function processing.

The methods it accepts take the broker as an example.

func Broker(b *broker.Broker) Option {
	return func(o *Options) {
		o.Broker = b
	}
}
Copy the code

The Broker method is a bit convoluted and should be looked at in conjunction with the previous two Init methods. Here the Decorator pattern is used, and Brocker is a Decorator method

Entering cmd.init, the for loop executes the Decorator’s methods in turn.

So its set of operations is s.o.pts.cmd. opts to reuse components on service.opts.

Let’s go ahead and enter the method of c.ap.runandexitonError () and then a.run ().

func (a *App) Run(arguments []string) (err error) {
    ...
    ifa.Before ! = nil { err = a.Before(context)iferr ! = nil { fmt.Fprintf(a.Writer, "%v\n\n", err)
			ShowAppHelp(context)
			return err
		}
	}
	...
}
Copy the code

The Register location is in a.Before. This method does not define itself, but also uses cmd.Before. CMD. Go newCmd (opts… Before = cmd.Before, which is set here. The final assignment is in 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)) ... }... }Copy the code

Consul consul = consul Consul = consul Consul = consul Consul = consul Consul = consul Then * c.o.pts.Registry = r() where the Registry is finally set up.

Finally I know where to get Registry after a lot of trouble. Let’s go back to the Register function call above. Let’s go to the Register function of consulRegistry

func (c *consulRegistry) Register(s *registry.Service, opts ... registry.RegisterOption) error { ...iferr := c.Client.Agent().ServiceRegister(asr); err ! = nil {return err
	}
	...
}
Copy the code

This is a long function and its main function is to communicate with Consul. In Agent().Serviceregister (ASR), it makes a PUT request.

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))
	iferr ! = nil {return err
	}
	resp.Body.Close()
	return nil
}
Copy the code

At this point, Go-Micro registers the service on Consul. Consul will eventually connect your server and client, allowing them to communicate with each other.

To this point, we understand the service registration process of GO-Micro from the source point of view, select the registry, and then register the service. The logic of other registries is the same and will not be repeated.

Here is the code flow chart I recorded

Figure 1.3

conclusion

Through go-Micro’s code Review, we learned the details of its internal implementation. Usually when we want to review the source code, the first step is to have an overall understanding of the project structure, which can be known through documentation or package names. Once you understand its project structure, you can focus on one of the subcomponents of interest and then dive into the source code. While reading code with their own questions, and then to find answers, while learning some code writing, and finally to learn things down, I think this is the meaning of learning and reading source code.