Ribbon load balancing service call

summary

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. Ribbon client component provides a series of perfect configuration items, such as connection timeout, Retry, 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.

Official website
https://github.com/Netflix/ribbon/wiki/Getting-Started

LB load balancing

What is LB load balance

Simply put, it 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 include software Nginx, LVS, hardware F5, etc.

Ribbon local load balancing client VS Nginx server load balancing difference:

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

Centralized LB

That is, an independent LB facility (which can be hardware, such as F5, or software, such as nginx) is used between the service consumer and the service provider, and the facility is responsible for forwarding the access request to the service provider through some policy;

In process LB

Integrate LB logic into the consumer. The consumer knows which addresses are available from the service registry, and then selects a suitable 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.

Ribbon load balancing demo

Ribbon works in two steps

The first step is to select Eureka server, which gives priority to servers with less load in the same region
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.

summary

Ribbon is actually a client component of soft load balancing,
It can be used in combination with other clients that need requests, and the combination with eureka is just one example.

Change pom

Introduce spring cloud starter ribbon dependency

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

Spring cloud starter Netflix eureka client has its own spring cloud starter ribbon dependency, so there is no need to reference the ribbon separately, otherwise the dependency version conflict may occur, which can also be solved by excluding the ribbon in eureka.

Ribbon core component IRule

IRule: select a service to be accessed from the service list according to a specific algorithm

com. netflix. loadbalancer. Roundrobin rule polling
com.netflix.loadbalancer.RandomRule random
com.netflix.loadbalancer.RetryRule first obtains the service according to the roundrobin rule policy. If it fails to obtain the service, it will retry within the specified time to obtain the available service
WeightedResponseTimeRule is an extension of roundrobin rule. The faster the response speed, the greater the instance selection weight, and the easier it is to be selected
BestAvailableRule 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
Availability filtering rule filters out failed instances first, and then selects smaller concurrent instances
ZoneAvoidanceRule is 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

The default mode is polling:

@Configuration
public class ApplicationContextConfig {
    /**
     * RestTemplate It provides a variety of convenient methods to access remote Http services,
     * It is a simple and convenient template class for accessing restful services. It is the client template tool set provided by Spring for accessing restful services
     * @return
     */
    @Bean
    @LoadBalanced   //Use the @ LoadBalanced annotation to give RestTemplate the ability of load balancing
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
Modify load balancing rules:

The official documentation clearly warns:
This custom configuration class cannot be placed under the current package and sub package scanned by @ ComponentScan,
Otherwise, the configuration class we customized will be shared by all Ribbon clients, and the purpose of customization will not be achieved.

/**
 * Replace ribbon load balancing rule (polling by default)
 * This custom configuration class cannot be placed under the current package and sub package scanned by @ ComponentScan,
 * Otherwise, the configuration class we customized will be shared by all Ribbon clients, and the purpose of customization will not be achieved.
 * @author zhangYang
 * @date 2022/4/10/0:16
 */
@Configuration
public class MySelfRule {
    /**
     * Replace with random
     * @return
     */
    @Bean
    public IRule myRule() {
        return new RandomRule();
    }
}
Add @ RibbonClient to the main startup class
/**consumer
 * @author yangzhang
 * @date 2022/1/21/16:00
 */
@SpringBootApplication
@EnableEurekaClient
//When the microservice is started, we can load our custom Ribbon configuration class to make the configuration effective (modify the load balancing rule to random)
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
public class OrderMain8002 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain8002.class, args);
    }
}

Handwritten load balancing rule algorithm

Principle of load balancing algorithm:

The number of requests of the rest interface% the total number of server clusters = the subscript of the actual call server location. The count of the rest interface starts from 1 after each service restart.
List instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
For example: List [0] instances = 127.0.0.1:8002
   List [1] instances = 127.0.0.1:8001
8001 + 8002 are combined into clusters. They have two machines in total, and the total number of clusters is 2. According to the principle of polling algorithm:
When the total number of requests is 1: 1% 2 = 1 and the corresponding subscript position is 1, the service address obtained is 127.0.0.1:8001
When the total request digit is 2: 2% 2 = 0 and the corresponding subscript position is 0, the service address is 127.0.0.1:8002
When the total request digit is 3: 3% 2 =1 and the corresponding subscript position is 1, the service address obtained is 127.0.0.1:8001
When the total request digit is 4: 4% 2 = 0 and the corresponding subscript position is 0, the service address is 127.0.0.1:8002
And so on

Microservice Transformation:
@RestController
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;

    //Service discovery
    @Resource
    private DiscoveryClient discoveryClient;

    @PostMapping("/payment/create")
    public CommentResult create(@RequestBody Payment payment) {
        int result = paymentService.create(payment);
        if (result > 0) {
            return new CommentResult(200, "Insert data successfully, serverPort: " + serverPort, result);
        } else {
            return new CommentResult(444, "Failed to insert data", null);
        }
    }

    @GetMapping("/payment/get/{id}")
    public CommentResult getPaymentById(@PathVariable("id") Long id) {
        Payment payment = paymentService.getPaymentById(id);
        log.info("******Query results:{}", payment);
        if (payment != null) {
            return new CommentResult(200, "Query succeeded, serverPort: " + serverPort, payment);
        }else {
            return new CommentResult(444, "No corresponding record, query ID" + id, null);
        }
    }

    /**
     * For the microservices registered in eureka, the service information can be obtained through service discovery
     * @return
     */
    @GetMapping("/payment/discover")
    public Object discover() {
        //Get the list of micro services
        List<String> services = discoveryClient.getServices();
        for (String service : services) {
            log.info("*****service: " + service);
        }

        //Obtain specific microservice information according to the microservice name
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for (ServiceInstance instance : instances) {
            log.info(instance.getServiceId() + "\t" + instance.getHost() + "\t"
                    + instance.getPort() + "\t" + instance.getUri());
        }
        return this.discoveryClient;
    }

    /**
     * Custom load balancing algorithm rules
     * @return
     */
    @GetMapping(value = "/payment/lb")
    public String getPaymentLB() {
        return serverPort;
    }
}
ApplicationContextBean remove annotation @ LoadBalanced
@Configuration
public class ApplicationContextConfig {
    /**
     * RestTemplate It provides a variety of convenient methods to access remote Http services,
     * It is a simple and convenient template class for accessing restful services. It is the client template tool set provided by Spring for accessing restful services
     * @return
     */
    @Bean
    //@LoadBalanced / / use the @ LoadBalanced annotation to give RestTemplate the ability of load balancing
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
New interface
/**
 * Custom load balancing algorithm rules (the default @ LoadBalanced annotation must be added)
 * @author zhangYang
 * @date 2022/4/12/22:49
 */
public interface LoadBalancer {

    /**
     * Get the name of each micro service
     * @param serviceInstances
     * @return
     */
    ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
/**
 * Custom load balancing algorithm rules (the default @ LoadBalanced annotation must be added)
 * @author zhangYang
 * @date 2022/4/12/22:52
 */
@Component
public class MyLb implements LoadBalancer {

    //Atomic operation class, set the initial value to 0
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public final int getAndIncrement() {
        int current;
        int next;
        do {
            //Get value
            current = this.atomicInteger.get();
            next = current >= 2147483647 ? 0 : current + 1;
            //Based on the principle of CAS spin lock, if the expected value current is the initial value 0, it will be updated to next
        } while (!this.atomicInteger.compareAndSet(current, next));
        System.out.println("******Number of visits next:" + next);
        return next;
    }

    /**
     * Get the name of each micro service
     * @param serviceInstances
     * @return
     */
    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        //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.
        int index = getAndIncrement() % serviceInstances.size();
        System.out.println(serviceInstances.get(index) + "***********************");
        return serviceInstances.get(index);
    }
}
@RestController
public class OrderController {

//    public static final String PaymentUrl = "http://localhost:8001 "; / / stand alone service
//    Called by the microservice name registered on eureka
    public static final String PaymentUrl = "http://Cluster-payment-service "; / / cluster deployment

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private LoadBalancer loadBalancer;

    @GetMapping("/consumer/payment/get/{id}")
    public CommentResult getPayment(@PathVariable("id") Long id) {
        return restTemplate.getForObject(PaymentUrl + "/payment/get/" + id, CommentResult.class, id);
    }

    /**
     * The browser used by the client is a get request, but the underlying entity sends a post to call the server 8001
     * @param payment
     * @return
     */
    @GetMapping("/consumer/payment/create")
    public CommentResult create(Payment payment) {
        //Using restTemplate to access restful interface is very simple and rude.
        //(url, requestMap, ResponseBean.class) these three parameters represent
        //REST request address, request parameters and HTTP response are converted to the object type.
        return restTemplate.postForObject(PaymentUrl + "/payment/create", payment, CommentResult.class);
    }

    @GetMapping("/consumer/payment/getForEntity/{id}")
    public CommentResult<Payment> getPayment2(@PathVariable("id") Long id) {
        ResponseEntity<CommentResult> forEntity = restTemplate.getForEntity(PaymentUrl + "/payment/get/" + id, CommentResult.class);
        if (forEntity.getStatusCode().is2xxSuccessful()) {
            return forEntity.getBody();
        } else {
            return new CommentResult<>(444, "Operation failed!");
        }
    }

    /**
     * Custom load balancing algorithm rules (the default @ LoadBalanced annotation must be added)
     * @return
     */
    @GetMapping(value = "/consumer/payment/lb")
    public String getPaymentLB() {
        //Obtain specific microservice information according to the microservice name
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        System.out.println(instances + "=======================");
        if (null == instances || instances.size() <= 0) {
            return null;
        }
        ServiceInstance instance = loadBalancer.instances(instances);
        URI uri = instance.getUri();
        System.out.println(uri + "---------------------");
        return restTemplate.getForObject(uri + "/payment/lb", String.class);
    }
}

Test: http://localhost/consumer/payment/lb

Tags: Java Load Balance Spring Cloud ribbon

Posted by bravo14 on Wed, 13 Apr 2022 05:48:26 +0300