Consul Registry for Micro Services Series

Posted by AJW on Fri, 20 Mar 2020 04:53:02 +0100



Original Link: https://mrhelloworld.com/posts/spring/spring-cloud/consul-service-registry/

Netflix Eureka 2.X https://github.com/Netflix/eureka/wiki officially announced the suspension of development, but it has little impact on domestic users. On the one hand, Eureka 1.X series is mostly used in China, and the official is actively maintaining 1.X https://github.com/Netflix/eureka/releases.

The existing open source work on eureka 2.0 is discontinued. The code base and artifacts that were released as part of the existing repository of work on the 2.x branch is considered use at your own risk.

Eureka 1.x is a core part of Netflix's service discovery system and is still an active project.

Translation:

Existing open source work on eureka 2.0 has stopped.Code libraries and artifacts published on the 2.x branch as part of the existing working database are considered at your own risk.

Eureka 1.x is a core part of the Netflix service discovery system and remains an active project.

Although Eureka, Hystrix, and others no longer continue to develop or maintain, it does not affect usage for now. Thank you for Open Source anyway, and salute Netflix for Open Source.

On the other hand, Spring Cloud supports software for many service discoveries, including Eureka, the protagonist we're going to talk about today, Consul.Below is a comparison of the service discovery software and features supported by Spring Cloud.

  

Common registries

  

  • Netflix Eureka
  • Alibaba Nacos
  • HashiCorp Consul
  • Apache ZooKeeper
  • CoreOS Etcd
  • CNCF CoreDNS

  

Characteristic Eureka Nacos Consul Zookeeper
CAP AP CP + AP CP CP
Health examination Client Beat TCP/HTTP/MYSQL/Client Beat TCP/HTTP/gRPC/Cmd Keep Alive
Avalanche protection Yes Yes nothing nothing
Auto Logoff Instance Support Support I won't support it Support
access protocol HTTP HTTP/DNS HTTP/DNS TCP
Monitoring Support Support Support Support Support
Multiple Data Centers Support Support Support I won't support it
Synchronization across registries I won't support it Support Support I won't support it
SpringCloud integration Support Support Support Support

  

Introduction to Consul

  

Consul is an open source tool introduced by HashiCorp for service discovery and configuration of distributed systems.With other distributed service registration and discovery scenarios, Consul's scenario is more "one-stop". It has built-in service registration and discovery framework, distributed consistency protocol implementation, health check, Key/Value storage, multi-data center scenario, no longer needs to rely on other tools (such as ZooKeeper), and is easier to use.

Consul is written in the Go language and therefore is naturally portable (supports Linux, Windows, and Mac OS); the installation package contains only one executable file for easy deployment and seamless fit with lightweight containers such as Docker.

  

Consul Feature

  

  • Raft algorithm

  • Service Discovery
  • Health examination
  • Key/Value Storage
  • Multiple Data Centers
  • Supports http and dns protocol interfaces
  • Official web administration interface

  

Consul Role

  

  • client: client, stateless, forwards HTTP and DNS interface requests to the server-side cluster within the LAN.
  • Server: server, save configuration information, highly available clusters, the recommended number of servers per data center is 3 or 5.

  

First, there are two data centers in the diagram, Datacenter1 and Datacenter2.Consul is very good at supporting multiple data centers, with clients and servers inside each data center, and servers typically between 3 and 5. This balances stability and performance because more machines can slow data synchronization.However, there is no limit to the number of clients, which can be tens of thousands.

All nodes in the data center will join the Gossip protocol.This means there is a Gossip pool containing all the nodes in this data center.Clients do not need to configure server address information, and discovery of services is done automatically.Detecting failure nodes is not server-side, but distributed; this makes failure detection more scalable than localized heartbeat mechanisms.Data centers are used as message layers to broadcast messages when selecting leader s as an important event.

The servers in each data center are part of a node set in a single Raft.This means they work together and choose a single leader - a selected server with additional responsibilities.Leader handles all queries and things.Things must also be copied to all nodes in the node set as part of the synchronization protocol.Because of this requirement, when a non-leader server receives an RPC request, it forwards the request to the cluster leader.

Server-side nodes are also part of the WAN Gossip pool. Unlike WAN pools and LAN pools, they are optimized for high network latency and contain only the nodes of other Consul servers.The purpose of this pool is to allow data centers to discover each other with minimal consumption.Starting a new data center is as easy as joining an existing WAN Gossip.Because these servers are running in this pool, it also supports cross-data center requests.When a server receives a request for a different data center, it forwards it to a random server in the correct data center.That server might be forwarded to the local leader.

This results in very low data center coupling.However, cross-data center requests are relatively fast and reliable due to fault detection, connection caching, and reuse.

Overall, data is not replicated between different data centers.When a request is received for a resource in another data center, the local Consul server sends an RPC request to the remote Consul server and returns the result.If the remote data center is unavailable, the resource will also be unavailable, but this will not affect the local data center.In some special cases, limited datasets are copied and backed up across data centers, such as Consul's built-in ACL replication capabilities or external tools such as consul-replicate.

  

How Consul works

  

  

Service Discovery and Registration

  

When the service Producer starts, it will inform Consul of its Ip/host information by sending a request. After Consul receives the registration information for Producer, it will send a health check request to Producer every 10 seconds (default) to check whether the Producer is healthy.

  

Service Calls

  

When a Consumer requests a Product, it first gets a temporary table (temp table) from Consul that stores the IP and Port of the Product service, chooses one of the Producer's IP and Port from the temp table table table table, and then sends an access request based on that IP and Port; the temp table only contains the Producer information that has passed the health check and is updated every 10s (default).

  

Consul Installation

  

Eureka is actually a Servlet program running in the Servlet container; Consul is a third-party tool written in go language that needs to be installed and used separately.

  

download

  

Visit Consul's website: https://www.consul.io to download the latest version of Consul.

Multiple environment installations are supported, only some of them are shown in the screenshot.

  

install

  

To let you learn about installations in different environments, we install a single node on Windows and a clustered environment on Linux.

  

Single Node

  

There is only one consul.exe execution file in the compressed package.

cd to the corresponding directory, use cmd to start Consul

# -dev means development mode runs, and-server means service mode runs
consul agent -dev -client=0.0.0.0

For startup convenience, you can also create a script to start under the consul.exe sibling directory, as follows:

consul agent -dev -client=0.0.0.0
pause

Access management background: http://localhost:8500/Seeing the following image means that our Consul service started successfully.

  

Starter Consul Cases

  

Create Project

  

We create an aggregate project to explain Consul, starting with a pom parent project.

  

Add Dependency

  

  pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- Item coordinate address -->
    <groupId>com.example</groupId>
    <!-- Project module name -->
    <artifactId>consul-demo</artifactId>
    <!-- Project Version Name Snapshot Version SNAPSHOT,Official Version RELEASE -->
    <version>1.0-SNAPSHOT</version>

    <!-- inherit spring-boot-starter-parent rely on -->
    <!-- Use the inheritance method to achieve reuse, which can be used if the inheritance matches -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
    </parent>

    <!--
        Dependent component version numbers are defined centrally, but not introduced.
        When declared dependencies are used in a subproject, you can use an independent version number.
        This unifies the management of dependent versions used in projects
     -->
    <properties>
        <!-- Spring Cloud Hoxton.SR1 rely on -->
        <spring-cloud.version>Hoxton.SR1</spring-cloud.version>
    </properties>

    <!-- Project Dependency Manage Parent Projects Only Declare Dependencies, Sub Projects Need to Indicate Required Dependencies(Version information can be omitted) -->
    <dependencyManagement>
        <dependencies>
            <!-- spring cloud rely on -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

  

Service Provider service-provider

  

Create Project

  

Create a service-provider service provider project under the parent project you just created.

  

Add Dependency

  

  pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>service-provider</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- Inherit Parent Dependency -->
    <parent>
        <groupId>com.example</groupId>
        <artifactId>consul-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <!-- Project Dependency -->
    <dependencies>
        <!-- spring cloud consul rely on -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!-- spring boot actuator rely on -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- spring boot web rely on -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- lombok rely on -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- spring boot test rely on -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
  
</project>

  

configuration file

  

  application.yml

server:
  port: 7070 # port

spring:
  application:
    name: service-provider # apply name
  # Configure Consul Registry
  cloud:
    consul:
      # Access address of the registry
      host: localhost
      port: 8500
      # Service Provider Information
      discovery:
        register: true                                # Is registration required
        instance-id: ${spring.application.name}-01    # Register instance id (must be unique)
        service-name: ${spring.application.name}      # Service Name
        port: ${server.port}                          # Service Port
        prefer-ip-address: true                       # Whether to register using ip address
        ip-address: ${spring.cloud.client.ip-address} # Service Request ip

  

Entity Class

  

  Product.java

package com.example.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {

    private Integer id;
    private String productName;
    private Integer productNum;
    private Double productPrice;

}

  

Writing services

  

  ProductService.java

package com.example.service;

import com.example.pojo.Product;

import java.util.List;

/**
 * Commodity Services
 */
public interface ProductService {

    /**
     * Query commodity list
     *
     * @return
     */
    List<Product> selectProductList();

}

  ProductServiceImpl.java

package com.example.service.impl;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;

/**
 * Commodity Services
 */
@Service
public class ProductServiceImpl implements ProductService {

    /**
     * Query commodity list
     *
     * @return
     */
    @Override
    public List<Product> selectProductList() {
        return Arrays.asList(
                new Product(1, "Huawei Mobile Phone", 1, 5800D),
                new Product(2, "Lenovo notebook", 1, 6888D),
                new Product(3, "Mi Pad", 5, 2020D)
        );
    }

}

  

Control Layer

  

  ProductController.java

package com.example.controller;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /**
     * Query commodity list
     *
     * @return
     */
    @GetMapping("/list")
    public List<Product> selectProductList() {
        return productService.selectProductList();
    }

}

The project can be tested by unit tests or by using postman or browser directly through url.

  

Startup Class

  

  ServiceProviderApplication.java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ServiceProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceProviderApplication.class, args);
    }

}

  

Visit

  

Access management background: http://localhost:8500/Seeing the following image means that our service is registered with the registry.

  

Copy the service-provider project with a modified port of 7071 and a registered instance id of 02.

spring:
  application:
    name: service-provider # apply name
  # Configure Consul Registry
  cloud:
    consul:
      # Access address of the registry
      host: localhost
      port: 8500
      # Service Provider Information
      discovery:
        register: true                                # Is registration required
        instance-id: ${spring.application.name}-02    # Register instance id (must be unique)
        service-name: ${spring.application.name}      # Service Name
        port: ${server.port}                          # Service Port
        prefer-ip-address: true                       # Whether to register using ip address
        ip-address: ${spring.cloud.client.ip-address} # Service Request ip

# port
server:
  port: 8602

  

The result of starting service-provider02 is as follows:

  

Service consumer service-consumer

  

Create Project

  

Create a service-consumer service consumer project under the parent project you just created.

  

Add Dependency

  

  pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>service-consumer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- Inherit Parent Dependency -->
    <parent>
        <groupId>com.example</groupId>
        <artifactId>consul-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <!-- Project Dependency -->
    <dependencies>
        <!-- spring cloud consul rely on -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!-- spring boot actuator rely on -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- spring boot web rely on -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- lombok rely on -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- spring boot test rely on -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

</project>

  

configuration file

  

  application.yml

server:
  port: 9090 # port

spring:
  application:
    name: service-consumer # apply name
  # Configure Consul Registry
  cloud:
    consul:
      # Access address of the registry
      host: localhost
      port: 8500
      # Service Provider Information
      discovery:
        register: false                               # Is registration required
        instance-id: ${spring.application.name}-01    # Register instance id (must be unique)
        service-name: ${spring.application.name}      # Service Name
        port: ${server.port}                          # Service Port
        prefer-ip-address: true                       # Whether to register using ip address
        ip-address: ${spring.cloud.client.ip-address} # Service Request ip

  

Entity Class

  

  Product.java

package com.example.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {

    private Integer id;
    private String productName;
    private Integer productNum;
    private Double productPrice;

}

  

  Order.java

package com.example.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {

    private Integer id;
    private String orderNo;
    private String orderAddress;
    private Double totalPrice;
    private List<Product> productList;

}

  

Consumer Services

  

  OrderService.java

package com.example.service;

import com.example.pojo.Order;

public interface OrderService {

    /**
     * Query orders based on primary key
     *
     * @param id
     * @return
     */
    Order selectOrderById(Integer id);

}

  OrderServiceImpl.java

package com.example.service.impl;

import com.example.pojo.Order;
import com.example.pojo.Product;
import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * Query orders based on primary key
     *
     * @param id
     * @return
     */
    @Override
    public Order selectOrderById(Integer id) {
        return new Order(id, "order-001", "China", 22788D,
                selectProductListByLoadBalancerAnnotation());
    }

    private List<Product> selectProductListByLoadBalancerAnnotation() {
        // ResponseEntity: Encapsulated return data
        ResponseEntity<List<Product>> response = restTemplate.exchange(
                "http://service-provider/product/list",
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<Product>>() {
                });
        return response.getBody();
    }

}

  

Control Layer

  

  OrderController.java

package com.example.controller;

import com.example.pojo.Order;
import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * Query orders based on primary key
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Order selectOrderById(@PathVariable("id") Integer id) {
        return orderService.selectOrderById(id);
    }

}

  

Startup Class

  

  ServiceConsumerApplication.java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class ServiceConsumerApplication {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ServiceConsumerApplication.class, args);
    }

}

  

Visit

  

Access: http://localhost:9090/order/1 The results are as follows:

  

Consul Cluster

  

The diagram above is a simple Consul Cluster architecture with Server and Client roles.Whether Server or Client, collectively referred to as Agent, Consul Client is relatively stateless and has little overhead of forwarding RPCs to Server resources.Server is a proxy with an extended set of capabilities including participating in Raft elections, maintaining cluster status, responding to RPC queries, interacting with other data centers on WAN Gossip, and forwarding queries to leader s or remote data centers.

For each data center, Client and Server are mixed.It is generally recommended that you have 3-5 servers.This is a trade-off between availability and performance in case of failure, because the more machines join, the slower consensus will be reached, and a Leader will be elected between Servers.However, there is no limit to the number of Clients. It is generally recommended that a service correspond to one Client, which can easily be extended to thousands or tens of thousands.At development time, we can just bind a set of clients in the service registry.

  

Environmental preparation

  

Server IP Consul Type Node Node
192.168.10.101 server server-01
192.168.10.102 server server-02
192.168.10.103 server server-03
192.168.10.1 client client-01

  

install

  

Upload the installation package to the server.

Install the unzip command, create a consul directory, and unzip the consul to the specified directory.

yum -y install unzip                                        # Install unzip
mkdir -p /usr/local/consul                                  # Create consul directory
unzip consul_1.7.0_linux_amd64.zip -d /usr/local/consul/    # Unzip to consul directory
mkdir -p /usr/local/consul/data                             # Create consul data directory

  

start-up

  

Registry Server

  

Run three registries in server service mode.

# node-01
./consul agent -server -bind=192.168.10.101 -client=0.0.0.0 -ui -bootstrap-expect=3 -data-dir=/usr/local/consul/data/ -node=server-01
# node-02
./consul agent -server -bind=192.168.10.102 -client=0.0.0.0 -ui -bootstrap-expect=3 -data-dir=/usr/local/consul/data/ -node=server-02
# node-03
./consul agent -server -bind=192.168.10.103 -client=0.0.0.0 -ui -bootstrap-expect=3 -data-dir=/usr/local/consul/data/ -node=server-03

The meaning of the parameter is as follows:

  • -server: Start as a server (registry)
  • -bind: indicates to which ip to bind

  • -client: Specifies the ip accessed by the client, 0.0.0 means unlimited client ip
  • -ui: Open web interface access

  • -bootstrap-expect=3: indicates that the minimum number of nodes in the server cluster is 3, below which it will not work properly (Note: like ZooKeeper, the number of clusters is usually odd for elections, and Consul uses the Raft algorithm)
  • -data-dir: Represents the directory where the specified data is stored (the directory must exist and need to be created in advance)
  • -node: Represents the name of the node as it appears in the web ui

  

Registry Client

  

consul agent -client=0.0.0.0 -bind=192.168.10.1 -data-dir=D:\Example\consol\data -node=client-01

  

Associated Clusters

  

Enter the following commands in the server-02 and server-03 and client-01 nodes to establish a cluster relationship.

./consul join 192.168.10.101

  

Cluster state

  

Enter the following command on any server to view all node information in the cluster.

./consul members

  

Visit

  

Access: http://192.168.10.101:8500/or http://192.168.10.102:8500/or http://192.168.10.103:8500/Cluster Any node can see the following interface indicating that the cluster environment has been successfully built.

  

test

  

The configuration file and code for the service-provider and service-consumer projects do not need to be changed, so start the test directly.

Access: http://192.168.10.101:8500/, the results are as follows:

Access: http://localhost:9090/order/1 The results are as follows:

After the test service is available, all the points of knowledge in the Consul Service Registry are explained.

This paper uses Knowledge Sharing "Signature - Non-Commercial Use - No Deduction of 4.0 International" License Agreement.

You can classification See more about Spring Cloud Article.

  

_Your comments and forwards are my greatest support.

*_Scavenger watches. Every article in Mr. Ward's Document + Video has special video explanations. Learning is easier, oh~

Topics: Java Spring Lombok Maven