Spring cloud Ribbon for service call

Pre content
(1),Introduction to microservice theory and take you hand-in-hand to build a microservice environment, pay and write orders
(2),Eureka service registration and discovery in spring cloud
(3),Spring cloud Zookeeper for service registration and discovery
(4),Consul of spring cloud for service registration and discovery

1,Robbon

1.1 Ribbon overview

(1) What is Ribbon?

  • Spring cloud ribbon is a set of client-side load balancing tools based on Netflix Ribbon.
  • In short, Ribbon is an open source project released by Netflix. Its main function is to provide software load balancing algorithms and service calls on the client. The Ribbon client component provides a series of perfect configurations, such as connection timeout, recovery, etc. To put it simply, list all the machines behind the Load Balancer (LB) in the configuration file. The Ribbon will automatically help you connect these machines based on certain rules (such as simple polling, random connection, etc.). We can easily use Ribbon to implement a custom load balancing algorithm.
  • In a word, load balancing + RestTemplate call.

(2) , Ribbon's official website

(3) . load balancing (LB)

  • Load balancing is to distribute the user's requests equally to multiple services, so as to achieve the HA (high availability) of the system. Common load balancing consists of software nginx, LVS and F5.
  1. Centralized LB: an independent LB facility (either hardware, such as F5, or software, such as nginx) is used between the service consumer and the service provider. The facility is responsible for forwarding the access request to the service provider through a certain policy.
  2. In process LB: integrate LB logic into the consumer. The consumer knows which addresses are available from the service registry, and then selects an appropriate server from these addresses. Ribbon belongs to in-process lb, which is just a class library integrated into the consumer process, through which the consumer obtains the address of the service provider.

(4) The difference between Ribbon local load balancing client and Nginx server load balancing

  1. Nginx is server load balancing. All client requests will be handed over to nginx to forward requests. That is, load balancing is realized by the server.
  2. Ribbon local load balancing, when calling the micro service interface, will obtain the registration information list in the registry and cache it to the JVM local, so as to realize the RPC remote service call technology locally.

1.2 Ribbon load balancing demonstration

(1) . architecture description

  • Ribbon is actually a client component of soft load balancing. It can be used in combination with other clients requiring requests. The combination with eureka is just one example.

Ribbon works in two steps

  1. The first step is to select Eureka server, which gives priority to servers with less load in the same region.
  2. The second step is to select an address from the service registration list obtained from the server according to the policy specified by the user. Among them, Ribbon provides a variety of strategies: such as polling, random and weighting according to response time.

(2) . POM file

  • Therefore, the jar package for integrating Ribbon is included in the integration package introduced into Eureka.
  • Therefore, the alternate access mode of 8001 and 8002 implemented earlier is the so-called load balancing.

(3) . description of RestTemplate

  1. getForObject: the returned object is the object converted from the data in the response body, which can be basically understood as Json.
  2. getForEntity: the returned object is the ResponseEntity object, which contains some important information in the response, such as response header, response status code, response body, etc.
  3. postForObject
  4. postForEntity

1.3. Ribbon core component IRule

1. Main load rules

  1. Roundrobin rule: polling
  2. RandomRule: random
  3. RetryRule: first obtain the service according to the roundrobin rule policy. If the service acquisition fails, it will be retried within the specified time
  4. WeightedResponseTimeRule: an extension of roundrobin rule. The faster the response speed, the greater the instance selection weight, and the easier it is to be selected
  5. Best available rule: it will first filter out the services in the circuit breaker tripping state due to multiple access faults, and then select a service with the least concurrency
  6. Availability filtering rule: filter out failed instances first, and then select smaller concurrent instances
  7. ZoneAvoidanceRule: the default rule, which determines the performance of the region where the server is located and the availability of the server, and selects the server

2. How to replace load rules

  1. Modify the configuration under cloud-consumer-order80 package.

  2. Our own customized configuration class cannot be placed under the current package and sub package scanned by @ ComponentScan, otherwise our customized configuration class will be shared by all Ribbon clients and cannot achieve the purpose of specialization and customization.

  3. On COM Create a new sub package of myrule under the package of Xiao.

  1. Create a new MySelfRule configuration class under myrule package
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MySelfRule {

    @Bean
    public IRule getRandomRule(){
        return new RandomRule(); // New random access load rule
    }
}

  1. Modify the main startup class as follows:
import com.xiao.myrule.MySelfRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

  1. test result
  • The results are accessed in a random manner as we have recently configured.

1.4. Ribbon load balancing algorithm

1.4.1 principle of polling algorithm

  • Load balancing algorithm: the number of requests of the rest interface% the total number of server clusters = the subscript of the actual calling server location. The count of the rest interface starts from 1 after each service restart.

1.4.2 source code of roundrobin rule

import com.netflix.client.config.IClientConfig;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RoundRobinRule extends AbstractLoadBalancerRule {
    private AtomicInteger nextServerCyclicCounter;
    private static final boolean AVAILABLE_ONLY_SERVERS = true;
    private static final boolean ALL_SERVERS = false;
    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public RoundRobinRule() {
        this.nextServerCyclicCounter = new AtomicInteger(0);
    }

    public RoundRobinRule(ILoadBalancer lb) {
        this();
        this.setLoadBalancer(lb);
    }

    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        } else {
            Server server = null;
            int count = 0;

            while(true) {
                if (server == null && count++ < 10) {
                	// Get the service provider with status up
                    List<Server> reachableServers = lb.getReachableServers();
                    // Get all service providers
                    List<Server> allServers = lb.getAllServers();
                    int upCount = reachableServers.size();
                    int serverCount = allServers.size();
                    
                    if (upCount != 0 && serverCount != 0) {
                    	// Obtain the subscript obtained from the module and the relevant service provider
                        int nextServerIndex = this.incrementAndGetModulo(serverCount);
                        server = (Server)allServers.get(nextServerIndex);

                        if (server == null) {
                            Thread.yield();
                        } else {
                            if (server.isAlive() && server.isReadyToServe()) {
                                return server;
                            }

                            server = null;
                        }
                        continue;
                    }

                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }

                if (count >= 10) {
                    log.warn("No available alive servers after 10 tries from load balancer: " + lb);
                }

                return server;
            }
        }
    }

    private int incrementAndGetModulo(int modulo) {
        int current;
        int next;
        do {
        	// First add and then take the mold
            current = this.nextServerCyclicCounter.get();
            next = (current + 1) % modulo;
            // CAS judges that if the judgment is successful, it will return true, otherwise it will continue to spin
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next));

        return next;
    }
}

1.4.3 handwriting polling algorithm

1. Modify the Controller of the payment module

Add the following

@GetMapping(value = "/payment/lb")
public String getPaymentLB(){
      return ServerPort;
}

2. Remove the @ LoadBalanced annotation from applicationcontextconfig

3. LoadBalancer interface

import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

public interface LoadBalancer {
    //Collect the total number of servers that can provide services and put them in the list
    ServiceInstance instances(List<ServiceInstance> serviceInstances);

}

4. Write MyLB class

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Component
public class MyLB implements LoadBalancer {

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    //coordinate
    private final int getAndIncrement(){
        int current;
        int next;
        do {
            current = this.atomicInteger.get();
            next = current >= 2147483647 ? 0 : current + 1;
        }while (!this.atomicInteger.compareAndSet(current,next));  //The first parameter is the expected value, and the second parameter is the modified value
        System.out.println("*******Number of visits next: "+next);
        return next;
    }

    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {  //Get a list of machines
        int index = getAndIncrement() % serviceInstances.size(); //Get the subscript location of the server
        return serviceInstances.get(index);
    }
}

5. Modify OrderController class

import com.xiao.cloud.entities.CommonResult;
import com.xiao.cloud.entities.Payment;
import com.xiao.cloud.lb.LoadBalancer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.net.URI;
import java.util.List;

@RestController
@Slf4j
public class OrderController {

    // public static final String PAYMENT_URL = "http://localhost:8001";
    public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private LoadBalancer loadBalancer;

    @Resource
    private DiscoveryClient discoveryClient;

    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment>   create( Payment payment){
        return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class);  //Write operation
    }

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
    }

    @GetMapping("/consumer/payment/getForEntity/{id}")
    public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
        if (entity.getStatusCode().is2xxSuccessful()){
            //  log.info(entity.getStatusCode()+"\t"+entity.getHeaders());
            return entity.getBody();
        }else {
            return new CommonResult<>(444,"operation failed");
        }
    }

    @GetMapping(value = "/consumer/payment/lb")
    public String getPaymentLB(){
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        if (instances == null || instances.size() <= 0){
            return null;
        }
        ServiceInstance serviceInstance = loadBalancer.instances(instances);
        URI uri = serviceInstance.getUri();
        return restTemplate.getForObject(uri+"/payment/lb",String.class);
    }
}

6. Test results

  • Finally, polling access is performed between 8001 and 8002.
  • The console output is as follows

7. Schematic diagram of package structure

Keywords: Back-end Spring Cloud Microservices ribbon

Added by smacpettit on Mon, 10 Jan 2022 03:35:45 +0200