Ribbon load balancing of spring cloud microservices

Keywords: Java Maven Spring Apache

What is microservice? What is spring cloud?

Microservice is a kind of architecture pattern, which advocates to divide an application into many tiny services. Services and services coordinate and cooperate with each other. Each service running is an independent process, and lightweight communication mechanism is adopted between services and services. In short, it is to divide a huge and complex single application into n multiple micro services (one service is responsible for one business), and each micro service is deployed independently.

The difference between microservices and clusters is that each deployed service of microservices is different, while each deployed service of clusters is the same.

Spring Cloud is a microservice framework. Compared with RPC frameworks such as Dubbo, Spring Cloud provides a complete set of distributed system solutions. It is built on the basis of spring boot. Spring Cloud's five beasts are Eureka, Ribbon, Feign, Hystrix and Zuul.

Environment building

Version specification

springboot: 2.0.3

springcloud: Finchley

Create a parent project (maven project)

 

Select maven and click next

 

Then click Finish

Modify the parent project 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.lzh</groupId>
    <artifactId>my-springcloud-01</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.RELEASE</version>
                <!--<version>Dalston.SR1</version>-->
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.0.3.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.0.4</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.0.31</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.0</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>1.2.3</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>${log4j.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <finalName>microservicecloud</finalName>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <delimiters>
                        <delimit>$</delimit>
                    </delimiters>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

Create subproject (maven project)

Project Name: microservice-consumer-80

pom.xml file

<?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">
    <parent>
        <artifactId>my-springcloud-01</artifactId>
        <groupId>com.lzh</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>microservice-consumer-80</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.lzh</groupId>
            <artifactId>microservice-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!-- integration ribbon -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

</project>

 

Encoding implementation

Add @ EnableDiscoveryClient annotation to the main startup class

Create a configuration file ConfigBean.java, new a RestTemplate remote call template, add @ LoadBalanced annotation, and enable load balancing

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @author lzh
 * create 2019-11-05-9:58
 */
@Configuration
public class ConfigBean {

    @Bean
    //Add load balancing
    //Spring Cloud Ribbon is a set of client load balancing tools based on Netflix Ribbon
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

}

In the DeptServiceImpl.java class of the service layer, use the RestTemplate template tool to call. Here, use the service name to call. If the service name is the same, it will make a sequential polling call

import com.lzh.cloud.model.Dept;
import com.lzh.cloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * @author lzh
 * create 2019-11-05-10:27
 */
@Service
public class DeptServiceImpl implements DeptService {


    /**
     * Using restTemplate to access restful interface is very simple and crude.
     * (url, requestMap, ResponseBean.class)These three parameters represent
     * REST The object type to which the request address, request parameter, and HTTP response transformation are converted.
     */
    @Autowired
    private RestTemplate restTemplate;

    //private static final String REST_URL_PREFIX = "http://localhost:8001";
    private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";

    @Override
    public boolean add(Dept dept) {
        return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
    }

    @Override
    public Dept get(Long id) {
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
    }

    @Override
    public List<Dept> list() {
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list/", List.class);
    }

    @Override
    public Object discovery() {
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/discovery", Object.class);
    }
}

Custom polling rules

Create a configuration MySelRule class in the previous package of the main startup class. Be careful not to create it in the same package as the main startup class or in the sub package of the main startup class

import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author lzh
 * create 2019-05-19-17:10
 */

@Configuration
public class MySelfRule {

    @Bean
    public IRule myRule(){
        //return new RandomRule(); //Ribbon is polling by default, and I customize it as random
        //Return new roundrobin rule(); / / ribbon is polling by default, and I define it as random

        return new RandomRule_LZH(); // I customize it five times for each machine
    }

}

Refer to the source code, and customize the polling algorithm randomrule · lzh.java

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

import java.util.List;

public class RandomRule_LZH extends AbstractLoadBalancerRule {

    // total = 0 / / when total = 5, we can move the pointer down,
    // index = 0 / / the address of the server currently providing external services,
    // total needs to be reset to zero, but it has reached 5 times. Our index = 1
    // Analysis: we have five times, but there are only three microservices (8001, 8002, 8003), OK?
    //


    private int total = 0;            // The total number of calls. Currently, each set is required to be called 5 times
    private int currentIndex = 0;    // Machine number of the current service

    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;

        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> upList = lb.getReachableServers();
            List<Server> allList = lb.getAllServers();

            int serverCount = allList.size();
            if (serverCount == 0) {
                /*
                 * No servers. End regardless of pass, because subsequent passes only get more
                 * restrictive.
                 */
                return null;
            }

//            int index = rand.nextInt(serverCount);// java.util.Random().nextInt(3);
//            server = upList.get(index);


//            private int total = 0; / / the total number of calls. At present, each set is required to be called 5 times
//            private int currentIndex = 0; / / the machine number of the current service
            if (total < 5) {
                server = upList.get(currentIndex);
                total++;
            } else {
                total = 0;
                currentIndex++;
                if (currentIndex >= upList.size()) {
                    currentIndex = 0;
                }
            }


            if (server == null) {
                /*
                 * The only time this should happen is if the server list were somehow trimmed.
                 * This is a transient condition. Retry after yielding.
                 */
                Thread.yield();
                continue;
            }

            if (server.isAlive()) {
                return (server);
            }

            // Shouldn't actually happen.. but must be transient or a bug.
            server = null;
            Thread.yield();
        }

        return server;

    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        // TODO Auto-generated method stub

    }

}

Finally, don't forget to annotate the main startup class and use the custom polling algorithm configuration = MySelfRule.class

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;


/**
 * @author lzh
 * create 2019-11-04-22:26
 */
@SpringBootApplication
@EnableEurekaClient
//Custom load balancing rules
//When the service is started, our custom Ribbon configuration class can be loaded to make the configuration effective
//configuration = MySelfRule.class load the MySelfRule configuration class into the container
@RibbonClient(name = "MICROSERVICECLOUD-DEPT",configuration = MySelfRule.class)
@EnableDiscoveryClient
public class MyConsumerApplication_80 {
    public static void main(String[] args) {
        SpringApplication.run(MyConsumerApplication_80.class);
    }
}

Code

 github: https://github.com/LZHDonald/my-springcloud-01/tree/master/microservice-consumer-80

Posted by lplatz on Tue, 17 Mar 2020 08:32:39 -0700