Spring Cloud Learning Notes: Using Ribbon Load Balancing

brief introduction

Spring Cloud Ribbon is a client load balancing tool based on Netflix Ribbon. After registering the Ribbon client in the registry, Ribbon can automatically help service consumers call interfaces based on some load balancing algorithm, such as polling (default), random, weighted polling, weighted random, etc.

Project introduction

  1. sc-parent, parent module (see Spring Cloud Learning Notes (1): Eureka Registry)
  2. sc-eureka, Registry (see Spring Cloud Learning Notes (1): Eureka Registry)
  3. sc-provider-random, provider of random ports
  4. sc-consumer, a consumer using Ribbon load balancing

Provider of random ports

1. Create the sub-module project sc-provider-random, pom.xml under the parent module:

<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>
  <parent>
    <groupId>com.cf</groupId>
    <artifactId>sc-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>sc-provider-random</artifactId>
  
  <dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  </dependencies>
</project>

2. Create the startup class provider.ProviderApplication:

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

@SpringBootApplication
public class ProviderApplication {

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

3. Create Controller: provider.controller.BookController

package provider.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/book")
@RestController
public class BookController {
    
    @GetMapping("/list")
    public String getBookList(){
        System.out.println("------------I was interviewed.-----------");
        return "[\"Java Beginning to Abandoning\",\"C++Beginning to Abandoning\",\"Python Beginning to Abandoning\",\"C Beginning to Abandoning\"]";
    }
}

4. Create application.yml:

server:
  port: 0 #By default 8080, the configuration of random ports needs to be set to 0

spring:
  application:
    name: sc-provider-random
eureka:
  client:
    serviceUrl: 
      defaultZone: http://localhost:8080/eureka/
  instance:
    instance-id: ${spring.application.name}:${random.value} #Random generation of instance names

5. Start the registry sc-eureka and two providers sc-provider-random in turn. The providers sc-provider-random have different start ports each time. You can see that two instances of sc-provider-random are registered in the registry:

Consumers using Ribbon load balancing

1. Create the sub-module project sc-consumer, pom.xml under the parent module:

<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>
  <parent>
    <groupId>com.cf</groupId>
    <artifactId>sc-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>sc-consumer</artifactId>
  
  <dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
    <!-- spring-cloud-starter-netflix-eureka-client Dependency is already included ribbon -->
    <!-- <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency> -->
  </dependencies>
</project>

2. Create the startup class consumer.ConsumerApplication:

package consumer;
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 ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
    
    //Integrating Ribbon for RestTemplate to provide load balancing capabilities
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

3. Create Controller: consumer.controller.ConsumerController that invokes provider services

package consumer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;
    
    @GetMapping("/getBookList")
    public String getBookList(){
        //sc-provider-random is the provider service name
        return restTemplate.getForObject("http://sc-provider-random/book/list", String.class);
    }
}

Note: When using Ribbon load balancing, service names cannot be underlined, otherwise valid hostname exceptions will occur

4. Create application.yml:

server:
  port: 8083

spring:
  application:
    name: sc-consumer
    
eureka:
  client:
    registerWithEureka: false
    serviceUrl:
      defaultZone: http://localhost:8080/eureka/    

5. Start the registry sc-eureka, two providers sc-provider-random and consumer sc-consumer in turn, and visit http://localhost:8083/getBook List:

After multiple visits, through the log printing of the console, it can be found that consumers access two provider instances by polling.

Change load balancing policy

By default, Ribbon uses Round Robin Rule (polling) as a load balancing strategy. We can implement the IRule interface or replace the default polling strategy with the existing load balancing strategy provided by Ribbon. As follows, the default polling is replaced by a random access policy and added to the consumer startup class:

    @Bean
    public IRule randomRule(){
        return new RandomRule();//Replace default polling with random access policy
    }

Custom Load Balancing Policy

A small example of a custom load balancing policy is still a polling access policy, which only accesses the next service instance five times per service instance.

1. Create the class consumer.rule.MyRule:

package consumer.rule;

import java.util.List;

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

public class MyRule extends AbstractLoadBalancerRule {
    private int currIndex = 0;//Current Service Instance Index
    
    private int currCount = 0;//Number of visits to the current service instance
    
    private static final int MAX_COUNT = 5;//Maximum number of visits per service instance is 5

    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 = chooseRandomInt(upList.size());
            server = upList.get(index);

            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;

    }

    protected synchronized int chooseRandomInt(int serverCount) {
        currCount++;
        if(currCount > MAX_COUNT){
            currIndex++;
            if(currIndex >= serverCount){
                currIndex = 0;
            }
            currCount = 1;
        }
        return currIndex;
    }

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

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

2. Modify configuration:

    @Bean
    public IRule randomRule(){
        return new MyRule();
    }

Start the registry sc-eureka, two providers sc-provider-random, and consumer sc-consumer in turn, and visit http://localhost:8083/getBook List for testing.

Keywords: Java Spring Maven Apache xml

Added by kkurkowski on Wed, 11 Sep 2019 13:39:54 +0300