Through the service discovery and registry, it is easy to manage the dynamic service instance information in the system. At the same time, it may also become the bottleneck and failure point of the system. Because the information for invocations between services comes from the service registry and discovery center, invocations between services may not work properly when it is unavailable. Therefore, service discovery and registries tend to be multi-instance deployments, providing high availability and stability.

We will implement service registration and discovery for Golang Web based on Consul. We will first interact with Consul via HTTP in a native way. We will then interact with Consul through the Consul Client interface provided by the Go Kit framework and compare the differences between them.

Consul installation and startup

Before this, we first need to build a simple Consul services, Consul of the download address for www.consul.io/downloads.h… In Unix (Mac and Linux), the downloaded file is a binary executable file that you can use to run Consul commands. Windows is an.exe executable.

Take the author’s Linux environment as an example, run the following command in the consul directory:

./consul version
Copy the code

Consul can be accessed directly from the consul version 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)
Copy the code

If we want to attribute Consul to a system command, we can move Consul to the /usr/local/bin file using the following command:

sudo mv consul /usr/local/bin/
Copy the code

We then launch Consul with the following command:

consul agent -dev
Copy the code

-dev Consul starts in development mode. In this mode, a one-node Consul service is rapidly deployed. The deployed node is both the Server and Leader. Starting in this mode is not recommended in a production environment because it does not persist any data, which only exists in memory.

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

Service registration and discovery interface

In order to reduce code duplication, we first define a Consul client interface, source located at ch7 – discovery/ConsulClient. Go under, the code as shown below,

type ConsulClient interface {

	@param serviceName serviceName * @param instanceId service instanceId * @param instancePort service instancePort * @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

	@param instanceId service instanceId */
	DeRegister(instanceId string, logger *log.Logger) bool

	/** * @param serviceName serviceName */
	DiscoverServices(serviceName string) []interface{}}Copy the code

The code provides three interfaces, which are:

  • Register, which is used for service registration. A service instance registers its own service name and service metadata in Consul.
  • DeRegister, which is used for service DeRegister. When the service is shut down, Consul requests Consul to DeRegister its metadata to avoid invalid requests.
  • DiscoverServices, which is used to DiscoverServices and request a service instance list from Consul based on the service name.

We then define a simple service main function that launches a Web server, registers its own service instance metadata with Consul using ConsulClient, provides a/Health endpoint for health checks, and logs itself out of Consul when the service goes offline. Source is located at ch7 – discovery/main/SayHelloService. Go, the code is as follows:

var consulClient ch7_discovery.ConsulClient
var logger *log.Logger

func main(a)  {

	} // 1. Instantiate a Consul client where the original implementation version is instantiated
	consulClient = diy.New("127.0.0.1".8500)
	// The instance failed and the service was stopped
	if consulClient == nil{
		panic(0)}// Get a service instance ID by going. Uuid
	instanceId := uuid.NewV4().String()
	logger = log.New(os.Stderr, "", log.LstdFlags)
	// Service registration
	if! consulClient.Register("SayHello", instanceId, "/health".10086.nil, logger) {
		// Failed to register, and the service failed to start
		panic(0)}// 2. Create a channel monitoring system signal
	exit := make(chan os.Signal)
	// Monitor only CTRL + C
	signal.Notify(exit, syscall.SIGINT, syscall.SIGTERM)
	var waitGroup sync.WaitGroup
	// Register the shutdown event and wait for the CTRL + C system signal to tell the service to shut down
	go closeServer(&waitGroup, exit, instanceId, logger)

	// 3. Start the HTTP server on the main thread
	startHttpListener(10086)

	// Wait for the close event to complete, and terminate the main thread
	waitGroup.Wait()
	log.Println("Closed the Server!")}Copy the code

In this simple microservice main function, we do the following:

  1. Instantiate ConsulClient and call the Register method to Register a service. The registered service name is SayHello, the service instance ID is generated by the UUID, the health check address is /health, and the service instance port is 10086.
  2. Register shutdown events to monitor service shutdown events. The closeServer method is called to log out of the service and shut down the HTTP server when the service is shut down.
  3. Start the HTTP server.

Before the service is closed, we call the ConsulClient#Deregister method to unregister the service instance from Consul with code in the closeServer method as follows:

func closeServer( waitGroup *sync.WaitGroup, exit <-chan os.Signal, instanceId string, logger *log.Logger)  {
	// Wait for shutdown notification
	<- exit
	// The main thread waits
	waitGroup.Add(1)
	// Service logout
	consulClient.DeRegister(instanceId, logger)
	// Shut down the HTTP server
	err := server.Shutdown(nil)
	iferr ! =nil{
		log.Println(err)
	}
	// The main thread can continue to execute
	waitGroup.Done()
}
Copy the code

In addition to performing service logout, the closeServer method also turns off the HTTP service for the local service. In the startHttpListener method, we registered three HTTP interfaces: /health for Consul’s health check, /sayHello for service availability, And /discovery to print information about service instances discovered from Consul, as shown below:

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()
	iferr ! =nil{
		logger.Println("Service is going to close...")}}Copy the code

CheckHealth is used to process a health check from Consul. In actual use, checkHealth can check instance performance and load and return valid health check information. The code looks like this:

func CheckHealth(writer http.ResponseWriter, reader *http.Request) c{
	logger.Println("Health check starts!")
	_, err := fmt.Fprintln(writer, "Server is OK!")
	iferr ! =nil{
		logger.Println(err)
	}
}
Copy the code

DiscoveryService gets the serviceName from the request parameter, calls the ConsulClient#DiscoverServices method to find a list of service instances for the corresponding service from Consul, and returns the result to response. The code looks like this:

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)
	iferr ! =nil{
		logger.Println(err)
	}
}
Copy the code

Knowing the complete ConsulClient architecture, we will begin writing an implementation of the core ConsulClient interface to complete the simple process of registering and discovering services between Consul and Consul.

summary

Only the service registration and discovery center is not enough, but also needs the full cooperation of each service instance, so that the whole service registration and discovery system can operate well. A service instance needs to do the following:

  • At the service startup stage, submit its own service instance metadata to the service discovery and registry center to complete service registration.
  • During service operation, the heartbeat is regularly maintained with the service registration and discovery center to ensure its online status. If possible, it also detects changes in its own metadata and resubmits the data to the Service Registration and Discovery Center when the service instance information changes;
  • When a service is shut down, an offline request is made to the Service Registration and Discovery Center to unregister itself from the service instance metadata in the registry.

The following articles will continue to implement microservice interactions with Consul’s registration and service queries.

The full code, from my Github,Github.com/longjoy/mic…

Phase to recommend

  1. Do you know the game theory developed by Von Neumann, the “father of modern computing”?
  2. How to learn ETCD? | I’m book

Read the latest article, pay attention to the public number: AOHO Qiusuo