Do it yourself to achieve Go service registration and discovery

Posted by darkninja_com on Sun, 05 Dec 2021 11:04:30 +0100

Hello, I'm aoho. What I share with you today is to realize the service registration and discovery of Go!

Through the service discovery and registration center, you can easily manage the dynamically changing service instance information in the system. At the same time, it may also become the bottleneck and failure point of the system. Because the call information between services comes from the service registration and discovery center, when it is unavailable, the call between services may not work normally. Therefore, service discovery and registration centers generally deploy multiple instances to provide high availability and stability.

We will implement Golang Web service registration and discovery based on consul. First, we will interact with Consul directly through HTTP through the original way; Then we will realize the interaction with Consul through the Consul Client interface provided by the Go Kit framework, and compare the differences between them.

Installation and startup of Consul

Before that, we need to build a simple consult service. The download address of consult is https://www.consul.io/downloads.html , Download according to different operating systems. In Unix Environment (Mac, Linux), the downloaded file is a binary executable file, which can directly execute the relevant commands of Consul. Window environment is an executable file of. exe.

Take the author's own Linux environment as an example, execute directly in the directory where the consumer file is located:

./consul version

You can directly get the version of the consumer you just downloaded:

Consul v1.5.1
Protocol 2 spoken by default,
understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)

If we want to attribute the consumption to the system command, we can use the following command to move the consumption to the / usr/local/bin file:

sudo mv consul /usr/local/bin/

Then we start Consul with the following command:

consul agent -dev

-The dev option indicates that Consul is started in the development mode. In this mode, a single node Consul service will be quickly deployed. The deployed node is both a Server and a Leader. Starting in this mode is not recommended in a production environment because it does not persist any data, which exists only in memory.

After startup, you can access it in the browser http://localhost:8500 Address, as shown in the figure:

Consul UI.png

Service registration and Discovery Interface

In order to reduce the repetition of the code, we first define a consult client interface. The source code is located under ch7-discovery / consult client.go. The code is as follows,

type ConsulClient interface {

 /**
  * Service registration interface
  * @param serviceName service name
  * @param instanceId Service instance Id
  * @param instancePort Service instance port
  * @param healthCheckUrl Health check address
  * @param meta Service instance metadata
  */
 Register(serviceName, instanceId, healthCheckUrl string, instancePort int, meta map[string]string, logger *log.Logger) bool

 /**
  * Service logoff interface
  * @param instanceId Service instance Id
  */
 DeRegister(instanceId string, logger *log.Logger) bool

 /**
  * Service Discovery Interface
  * @param serviceName service name
  */
 DiscoverServices(serviceName string) []interface{}
}

Three interfaces are provided in the code:

  • Register is used for service registration. The service instance registers its own service name and service metadata in consult;
  • DeRegister is used to log off the service. When the service is closed, it requests Consul to log off its metadata to avoid invalid requests;
  • DiscoverServices is used for service discovery. It requests Consul for the corresponding service instance information list through the service name.

Next, we define a simple service main function, which will start the Web server, use ConsulClient to register its own service instance metadata with Consul, provide a / health endpoint for health check, and log itself out of consul when the service goes offline. The source code is located in ch7 discovery / main / sayhelloservice.go. The code is as follows:

var consulClient ch7_discovery.ConsulClient
var logger *log.Logger

func main()  {

 // 1. Instantiate a consult client, where the original ecological implementation version is instantiated
 consulClient = diy.New("127.0.0.1", 8500)
 // Instance failed, stop service
 if consulClient == nil{
  panic(0)
 }

 // Get a service instance ID through go.uuid
 instanceId := uuid.NewV4().String()
 logger = log.New(os.Stderr, "", log.LstdFlags)
 // Service registration
 if !consulClient.Register("SayHello", instanceId, "/health", 10086, nil, logger) {
  // Registration failed, service startup failed
  panic(0)
 }

 // 2. Establish a channel monitoring system signal
 exit := make(chan os.Signal)
 // Monitor ctrl + c only
 signal.Notify(exit, syscall.SIGINT, syscall.SIGTERM)
 var waitGroup sync.WaitGroup
 // Register the shutdown event and wait for the ctrl + c system signal to notify the service shutdown
 go closeServer(&waitGroup, exit, instanceId, logger)

 // 3. Start the http server in the main thread
 startHttpListener(10086)

 // Wait for the execution of the shutdown event to end and end the main thread
 waitGroup.Wait()
 log.Println("Closed the Server!")

}

In this simple microservice main function, the following work is mainly carried out:

  1. Instantiate ConsulClient and call Register method to complete service registration. The registered service name is SayHello, the service instance ID is generated by UUID, the health check address is / health, and the service instance port is 10086;
  2. Register shutdown events and monitor service shutdown events. When the service is closed, call the closeServer method to log off the service and close the http server;
  3. Start the http server.

Before closing the service, we will call the ConsulClient#Deregister method to unregister the service instance from Consul. The code is in the closeServer method, as shown below:

func closeServer( waitGroup *sync.WaitGroup, exit <-chan os.Signal, instanceId string, logger *log.Logger)  {
 // Wait for shutdown information notification
 <- exit
 // Main thread waiting
 waitGroup.Add(1)
 // Service logoff
 consulClient.DeRegister(instanceId, logger)
 // Turn off the http server
 err := server.Shutdown(nil)
 if err != nil{
  log.Println(err)
 }
 // The main thread can continue execution
 waitGroup.Done()
}

The closeServer method not only logs off the service, but also shuts down the http service of the local service. In the startHttpListener method, we registered three http interfaces, / health is used to check the health of Consul, / sayHello is used to check whether the service is available, and / discovery is used to print the service instance information found from Consul. The code is as follows:

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 is used to process health checks from consult. We only return them directly and simply. In actual use, we can detect the performance and load of the instance and return valid health check information. The code is as follows:

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 obtains the serviceName from the request parameters, calls the consulclient#discovereservices method to find the service instance list of the corresponding service from Consul, and then returns the result to the response. The code is as follows:

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)
 }
}

To understand the complete microservice structure, we will begin to write the implementation of the core ConsulClient interface and complete the process of service registration and discovery between this simple microservice and Consul.

Summary

Only the service registration and discovery center is not enough. It also needs the full cooperation of each service instance to make the whole service registration and discovery system work well. A service instance needs to do the following:

  • In the service startup phase, submit its own service instance metadata to the service discovery and registration center to complete the service registration;
  • During the service operation phase, the service registration and Discovery Center shall regularly maintain the heartbeat to ensure its online status. If possible, it will also detect the change of its own metadata and resubmit the data to the service registration and discovery center when the service instance information changes;
  • When the service is closed, send a logoff request to the service registration and discovery center to log off its service instance metadata in the registry.

The following article will continue to realize the interaction between microservices and consult, such as registration and service query.

Complete code, from my Github, https://github.com/longjoy/micro-go-book