Location>code7788 >text

Deeper Understanding of Load Balancing Algorithms and Configuration Strategies in Microservices

Popularity:736 ℃/2024-08-14 09:25:36

In the last issue we explored communication between microservices in detail, specifically how to integrate with the Ribbon. briefly, when making RPC calls using the resttemplate class, we added an interceptor internally to implement load balancing. However, we did not discuss the specific load balancing algorithm in depth. Therefore, the focus of this section is to describe how to select the appropriate node from multiple replicas for a service call. This will help you better understand how to effectively implement load balancing in a microservices architecture.

Okay, today we will still cover the source code, but hopefully we will focus on the conceptual level rather than delving into each specific procedure call. There's no need to get hung up on specific lines of code, as it's important to understand the overall architecture and flow so that you can better grasp the essence of the topic.

Load Balancing Algorithms

Let's first explore the load balancing algorithm that the Ribbon uses by default. Some might say that it uses a polling algorithm because we often see the effects of polling when testing locally. However, simply relying on such superficial observations to answer interview questions is risky. In fact, neglecting to understand the source code in depth can lead to serious misunderstandings.

While practice is part of growing knowledge, in real production environments, especially when deployed across multiple data centers, we can't simply reduce the problem to a test environment with a local cluster.

Get server ip

Let's move on to the previous post and discuss how the steps for choosing a server are recapitulated below:

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
            throws IOException {
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        Server server = getServer(loadBalancer, hint);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        }
        RibbonServer ribbonServer = new RibbonServer(serviceId, server,
                isSecure(server, serviceId),
                serverIntrospector(serviceId).getMetadata(server));

        return execute(serviceId, ribbonServer, request);
    }

Get Load Balancer - ZoneAwareLoadBalancer

Let's take a look at the getServer method. What should we do with so many load balancers all of a sudden? The best thing to do at this point is to look at the autoconfiguration and see which ones are being injected in.

image

You don't need to look for the intermediate steps, I've already looked for them beforehand, and here they are:

image

This diagram contains two key pieces of information: first, an IRule rule is injected, and second, that IRule rule is applied to the ZoneAwareLoadBalancer load balancer. Okay, now we are clear about the next steps. Let's move on to look at the

    public Server chooseServer(Object key) {
        if (!() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            ("Zone aware logic disabled or there is only one zone");
            return (key);
        }
        Server server = null;
        try {
            //Omit redundant code
            Set<String> availableZones = (zoneSnapshot, (), ());
            ("Available zones: {}", availableZones);
            if (availableZones != null && () < ().size()) {
                String zone = (zoneSnapshot, availableZones);
                ("Zone chosen: {}", zone);
                if (zone != null) {
                    BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                    server = (key);
                }
            }
        } catch (Exception e) {
            ("Error choosing server using zone aware logic for load balancer={}", name, e);
        }
        //Omit redundant code
    }

If it's in our local environment, the first if branch is usually executed; but if it's in a production environment with multiple zones configured, the following branch is executed. Let's take a look.

Unconfigured regional situation

Let's take a look at the first scenario, i.e. how the load balancing rules are applied if there are no zones or only one zone. We will look at the code of the parent load balancer, BaseLoadBalancer.

public class BaseLoadBalancer extends AbstractLoadBalancer implements
        , IClientConfigAware {
    private final static IRule DEFAULT_RULE = new RoundRobinRule();

    protected IRule rule = DEFAULT_RULE;
    
    public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        ();
        if (rule == null) {
            return null;
        } else {
            try {
                return (key);
            } catch (Exception e) {
                ("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }
    
     void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
        // Omit part of the code
        setRule(rule);
        // Omit part of the code
    }
}

Here we can see that there is a default IRule rule - RoundRobinRule, but don't be impulsive, because our Spring auto-hosted IRule rule is not used yet, and it's not possible to go through the rounds so simply. We can see here is where the settings are. I've grabbed it out as well.

Finally let's take a look at our ZoneAwareLoadBalancer generation constructor, since we do bring in the rules during injection. Below is the relevant code example:

    public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
                                 IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
                                 ServerListUpdater serverListUpdater) {
        super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
    }

Here, the initWithConfig method of the BaseLoadBalancer class is eventually called when the super parent constructor is executed. I didn't trace down all of them, but in the end the ZoneAvoidanceRule's load balancing code is also quite complex. However, you can think of it as similar to polling in the absence of zones.

Configuration of multiregional scenarios

At this stage, the program will execute the second branch, in fact, the main code is shown below:

String zone = (zoneSnapshot, availableZones);
if (zone != null) {
    BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
    server = (key);
}

The goal is still to select a server, but limited to the current zone. Detailed discussion of this section is omitted, as the methods that follow are all about the ZoneAvoidanceRule's load balancing algorithm code.

How to configure other algorithms

In this case, how should I configure it if I want to use another load balancing algorithm instead of the current one? Actually, one can look at the injected source code and there are two ways to accomplish this. First, the desired load balancing algorithm can be specified by adding a configuration entry to the configuration class.

if ((, name)) {
    return (, config, name);
}

local configuration

Here you can see that we are also configuring via a configuration file, but the configuration file approach enables us to make local microservice load balancing choices. Let's take a look at the source code first:

    public PropertiesFactory() {
        (, "NFLoadBalancerClassName");
        (, "NFLoadBalancerPingClassName");
        (, "NFLoadBalancerRuleClassName");
        (, "NIWSServerListClassName");
        (, "NIWSServerListFilterClassName");
    }

    public boolean isSet(Class clazz, String name) {
        return (getClassName(clazz, name));
    }

When invoking specific microservices, you can use the appropriate load balancing policies to configure them as needed Documentation.

#Name of the microservice being called
mall-order.
 ribbon.
    #Specify to use the load balancing policy provided by Nacos (prioritize calls to instances in the same cluster, based on random & weights)
    NFLoadBalancerRuleClassName.

global configuration

In the global case it's even simpler to observe the use of the@ConditionalOnMissingBean annotation. If we manually load the appropriate bean in Spring, then this annotation will not take effect.

    @Bean
    public IRule ribbonRule() {
        // Specify to use the load balancing policy provided by Nacos (prioritize calls to instances in the same cluster, based on random weights)
        return new NacosRule();
    }

Quite simple, then so, in fact, we can also customize a strategy. After all, according to the first have copied down the fixed implementation of the method, their own in the realization of the method to write their own business logic is not finished.

Customized Strategies

It seems that there are a few key points for implementing other load balancing algorithmic strategies. First, it is necessary to inherit theAbstractLoadBalancerRule parent class and implement its abstract methods. Next, we can start writing our implementation code:

@Slf4j
public class XiaoYuRandomWithWeightRule extends AbstractLoadBalancerRule {

    @Override
    public Server choose(Object key) {
        //Just implement your own logic here
        return server;
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

OK, the rest is just a matter of configuring it locally or globally to make our rules take effect.

Only the configuration and customization of algorithmic rules are described here. In fact, the operation of load balancers is a similar routine. We will not repeat the demonstration here.

summarize

Today, we mainly added to the previous chapter on microservice communication and delved into the importance of load balancing algorithms. We begin with a detailed discussion of the load balancing algorithm used by Ribbon by default. While the effects of polling may be observed during local testing, simply relying on such superficial observations is not enough. In real production environments, especially when deployed across multiple data centers, the choice of load balancing policy requires more in-depth understanding and analysis.

We further analyzed how we can flexibly respond to various scenarios by configuring and customizing load balancing rules. Whether it is local or global configuration, we can adjust the behavior of load balancing according to specific needs. At the same time, we show how to extend Ribbon's load balancing capabilities with custom algorithms to better fit the needs of specific business scenarios.

In the next sections, we will dive deeper into OpenFeign components. Our focus will be on how to enable developers to focus more on business logic code rather than being forced to deal with the tedious details associated with RPC calls.


I'm Rain, a Java server-side coder, studying the mysteries of AI technology. I love technical communication and sharing, and I'm passionate about the open source community. I am also an excellent author of Nuggets, a content co-creator of Tencent Cloud, an expert blogger of Ali Cloud, and an expert of Huawei Cloud.

💡 I won't be shy about sharing my personal explorations and experiences on the path of technology, in the hope that I can bring some inspiration and help to your learning and growth.

🌟 Welcome to the effortless drizzle! 🌟