Detailed Ribbon

Table of contents

1 Overview

2. use

2.1. Import

2.2. Enable

2.3. Switch load balancing algorithm

3. Load balancing source code analysis

3.1. Interface

3.2. Abstract class

3.3. Select server

3.4. Atomicity

4. Custom load balancing algorithm


1 Overview

Ribbon is a client load balancing library open sourced by Netflix and one of the core components of the Spring Cloud Netflix project. It is mainly used to load balance services in the microservice architecture to improve the availability and performance of the system. The ribbon is not a communication component, but an intermediate layer between the service caller and the communication component. It is mainly used for load balancing and selecting a service node suitable for processing this request.

Now the communication components in the entire spring cloud system actually encapsulate the load balancing component + HTTP communication component. There is nothing to say about the HTTP communication component, which is used to initiate HTTP requests. There are also many HTTP communication components on the market, such as spring boot. RestTemplate for apache, Apache HttpClient for apache and more. It is worth exploring the load balancing component, which determines the distribution of requests as a load balancing component, and can be said to be the core of the entire communication component.

Starting from Spring Cloud 2020.0, Spring Cloud has officially marked Ribbon as deprecated, and Spring Cloud LoadBalancer is recommended as an alternative. This is based on the consideration of standardization. The entire spring ecosystem has always hoped to "inclusive of everything", so the community naturally does not want to directly adopt a fixed implementation on the load balancing component, but hopes that the three-party solution can be smooth. Access, switch.

Although ribbon will no longer be the first choice for load balancing components in the future, as the most classic load balancing component, some of its underlying ideas are still used in subsequent solutions. In fact, if you understand the source code of ribbon, you can basically understand the principle of load balancing. Everything remains the same.

2. use

2.1. Import

The blogger’s last article introduced in detail how to use eureka+communication components to complete service registration and call, which also introduced the use of ribbon in detail, you can move:

Detailed explanation of Eureka service registration and calling__BugMan's Blog-CSDN Blog

In general it is:

Eureka integrates ribbon. After importing eureka, there is no need to import ribbon separately, but it should be noted that the correct version number must be selected. Do not select a high version that has removed the ribbon. The dependent version used in this article:

Of course, you can also directly downgrade the version number of the entire spring cloud. Regarding the issue of the version number of spring cloud, you can move to another article of the blogger, which will explain in detail:

Explain the Spring Cloud version problem in detail_BugMan's Blog-CSDN Blog

2.2. Enable

After introducing dependencies, you can enable load balancing on the HTTP communication component. Take the RestTemplate that comes with spring boot as an example, so that every time the client (consumer) initiates an http request, it will perform load balancing operations on the local end. Then proceed to service access.

2.3. Switch load balancing algorithm

The core interface Irule of load balancing has multiple implementation classes, and each implementation class implements a different load balancing algorithm.

Commonly used are polling, random, available, retry, etc.:

  • RoundRobinRule, polling

  • RandomRule, random

  • AvailabilityFilteringRule, which filters out, trips, accesses faulty services, and polls the rest

  • RetryRule, will obtain the service according to the polling, if the service fails to obtain, it will retry within the specified time

  • When you need to switch, just inject @Bean directly into @Configuration:

3. Load balancing source code analysis

3.1. Interface

IRule interface:

  • choose: elect a server
  • get/setLoadBalancer: Get and modify the balancer

ILoadBalancer:

Dealing with the server, responsible for finding and registering the server

3.2. Abstract class

AbstractLoadBalancer implements the Irule interface, and rewrites the get/set method of the balancer, leaving only one abstract method—choose, to be rewritten by subclasses.

3.3. Select server

All implementation classes inherit the abstract class AbstractLoadBalancer. Each rewrites the choose method, that is, implements different load balancing rules (here takes the default load balancing rule of ribbon, RoundRobinRule as an example):

The choose method is the load balancing strategy, which is the core method of each load balancing class (here, the default load balancing rule of ribbon, RoundRobinRule, is taken as an example):

The balancer will interact with the registration center, and then record the relevant information of all servers in the current system, including the total number of servers, the total number of available servers, etc.

Ask the balancer for the total number of servers and the total number of available servers, and then perform calculations based on these two values ​​to select the server that carries the traffic.

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) {
                    List<Server> reachableServers = lb.getReachableServers();
                    List<Server> allServers = lb.getAllServers();
                    int upCount = reachableServers.size();
                    int serverCount = allServers.size();
                    if (upCount != 0 && serverCount != 0) {
                        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;
            }
        }
    }

The Server class that encapsulates server-related information throughout the process includes detailed parameters of the server:

 

3.4. Atomicity

During the selection process, in order to ensure atomicity, a spin lock (CAS) is used to ensure that only one access thread is processed each time, and the rest of the threads are in the self-selected waiting state:

compareAndSet(expected value, modified value)

The expected value, which is the version number.

Modification value, which is the value to update the version number to.

Determine whether the expected value has changed (whether it is the same before and after), and if the expected value has not changed, update the expected value to the modified value.

Returns true, otherwise returns false.

4. Custom load balancing algorithm

If there is a top-level interface and the load balancing algorithm can be switched, it is natural to customize the load balancing algorithm. The following is a load balancing algorithm for load balancing based on server weights:

public class CustomWeightedRandomRule extends AbstractLoadBalancerRule {
    private AtomicInteger totalWeight = new AtomicInteger(0);

    @Override
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        if (lb == null) {
            return null;
        }

        List<Server> allServers = lb.getAllServers();
        int serverCount = allServers.size();
        if (serverCount == 0) {
            return null;
        }

        int randomWeight = ThreadLocalRandom.current().nextInt(totalWeight.get());
        int currentWeight = 0;

        for (Server server : allServers) {
            currentWeight += getWeight(server);
            if (randomWeight < currentWeight) {
                return server;
            }
        }

        // Fallback to the default server if no server is selected
        return super.choose(key);
    }

    private int getWeight(Server server) {
        // Return the weight of the server (custom logic)
        // Example: return server.getMetadata().getWeight();
        return 1; // Default weight is 1
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        super.initWithNiwsConfig(clientConfig);
        // Calculate the total weight of all servers
        List<Server> allServers = getLoadBalancer().getAllServers();
        totalWeight.set(allServers.stream().mapToInt(this::getWeight).sum());
    }
}

Guess you like

Origin blog.csdn.net/Joker_ZJN/article/details/131150360