首页 > 编程语言 >深入理解微服务中的负载均衡算法与配置策略

深入理解微服务中的负载均衡算法与配置策略

时间:2024-08-14 09:29:59浏览次数:11  
标签:负载 算法 server IRule 均衡 public

上一期我们详细探讨了微服务之间的通信,特别是介绍了如何集成Ribbon。简单来说,通过使用resttemplate类进行RPC调用时,我们内部增加了一个拦截器来实现负载均衡。然而,我们并未深入讨论具体的负载均衡算法。因此,本章节的重点是介绍如何从多个副本中选择合适的节点进行服务调用。这将帮助大家更好地理解在微服务架构中如何有效地实现负载均衡。

好的,今天我们依旧会涉及源码,但希望大家能把注意力集中在理念层面,而不是深究每个具体的过程调用。无需纠结于代码的具体行数,因为重要的是理解整体架构和流程,这样才能更好地掌握主题的实质。

负载均衡算法

我们首先来探讨一下默认情况下Ribbon使用的负载均衡算法。有些人可能会说它使用轮询算法,因为在本地测试时,我们经常会看到轮询的效果。然而,简单地依赖这种表面的观察来回答面试题是有风险的。实际上,忽略了深入理解源代码可能会导致严重的误解。

尽管实践是增长知识的一部分,但是在真实的生产环境中,尤其是跨多个数据中心部署的情况下,我们无法简单地将问题简化为本地集群的测试环境。

获取服务器ip

我们接着上一篇内容,讨论如何选择服务器的步骤如下复述:

    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);
    }

获取负载均衡器——ZoneAwareLoadBalancer

我们来看看getServer方法,突然间出现这么多负载均衡器,应该怎么处理呢?这时候最好的方法就是查看自动配置,看看哪些被注入进来了。

image

中间步骤大家就不用再找了,我已经事先找好了,就在这里:

image

这张图包含两个关键信息:首先是注入了一个IRule规则,其次是将该IRule规则应用到了ZoneAwareLoadBalancer负载均衡器中。好的,现在我们清楚了接下来的步骤。接下来我们继续查看

    public Server chooseServer(Object key) {
        if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            logger.debug("Zone aware logic disabled or there is only one zone");
            return super.chooseServer(key);
        }
        Server server = null;
        try {
            //省略多余代码
            Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
            logger.debug("Available zones: {}", availableZones);
            if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
                String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
                logger.debug("Zone chosen: {}", zone);
                if (zone != null) {
                    BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                    server = zoneLoadBalancer.chooseServer(key);
                }
            }
        } catch (Exception e) {
            logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
        }
        //省略多余代码
    }

如果是在我们本地环境,通常会执行第一个if分支;但如果是在生产环境并配置了多个区域,那么会执行下面的分支。让我们一起来看看。

无配置区域情况

让我们来看看第一种情况,即如果没有区域或者只有一个区域,负载均衡规则是如何应用的。我们将查看父类负载均衡器BaseLoadBalancer的代码。

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

    protected IRule rule = DEFAULT_RULE;
    
    public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }
    
     void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
        // 省略部分代码
        setRule(rule);
        // 省略部分代码
    }
}

这里可以看到是有默认的IRule规则的——RoundRobinRule,但是别冲动,因为我们Spring自动托管的IRule规则还没用上,不可能这么简单的走轮训。我们可以看到这里是有设置的地方的。我也抓出来了。

最后让我们再来看看我们的ZoneAwareLoadBalancer生成构造器,因为在注入时我们是会带入规则的。以下是相关的代码示例:

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

在这里,当super父类构造器执行完毕后,最终会调用BaseLoadBalancer类的initWithConfig方法。我没有一一追踪下去,但最后ZoneAvoidanceRule的负载均衡代码也相当复杂。不过,你可以将其理解为在没有区域的情况下类似于轮询。

配置多区域情况

在这个阶段,程序将会执行第二个分支,实际上,主要的代码如下所示:

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

目的仍然是选择一个服务器,但是限定在当前区域内。关于这部分的详细讨论略去,因为接下来的方法都是关于ZoneAvoidanceRule的负载均衡算法代码。

如何配置其他算法

在这种情况下,如果我想使用其他负载均衡算法而不是当前的算法,应该如何配置呢?实际上,可以查看注入的源代码,有两种方法可以实现这一点。首先,可以通过在配置类中添加一个配置项来指定所需的负载均衡算法。

if (this.propertiesFactory.isSet(IRule.class, name)) {
    return this.propertiesFactory.get(IRule.class, config, name);
}

局部配置

在这里可以看到我们也是通过配置文件来进行配置的,不过配置文件的方式使我们能够进行局部微服务负载均衡的选择。让我们先来看一下源代码:

    public PropertiesFactory() {
        classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
        classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
        classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
        classToProperty.put(ServerList.class, "NIWSServerListClassName");
        classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
    }

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

在调用特定的微服务时,可以根据需要使用相应的负载均衡策略来配置 application.yml 文件。

#被调用的微服务名
mall‐order:
 ribbon:
    #指定使用Nacos提供的负载均衡策略(优先调用同一集群的实例,基于随机&权重)
    NFLoadBalancerRuleClassName:com.alibaba.cloud.nacos.ribbon.NacosRule

全局配置

在全局情况下更为简单,可以观察到在自动注入时使用了 @ConditionalOnMissingBean 注解。如果我们在Spring中手动加载了相应的bean,那么这个注解就不会生效了。

    @Bean
    public IRule ribbonRule() {
        // 指定使用Nacos提供的负载均衡策略(优先调用同一集群的实例,基于随机权重)
        return new NacosRule();
    }

相当简单了,那么这样的的话,其实我们也可以进行自定义一个策略的。毕竟照先有的抄下固定实现方法后,自己在实现方法内写上自己的业务逻辑不就完了。

自定义策略

看起来,对于实现其他的负载均衡算法策略,有几个关键点。首先,需要继承 AbstractLoadBalancerRule 父类,并且实现其抽象方法。接下来,我们可以开始编写我们的实现代码:

@Slf4j
public class XiaoYuRandomWithWeightRule extends AbstractLoadBalancerRule { 

    @Override
    public Server choose(Object key) {
        //这里实现自己的逻辑即可
        return server;
    }

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

OK,剩下的就按照局部配置或者全局配置下,让我们的规则生效即可。

在这里只讲述了算法规则的配置和自定义方法,实际上负载均衡器的操作也是类似的套路。这里就不重复演示了。

总结

今天,我们主要补充了上一章关于微服务通信的内容,并深入探讨了负载均衡算法的重要性。我们首先详细讨论了Ribbon默认使用的负载均衡算法。尽管在本地测试时可能会观察到轮询的效果,但简单依赖这种表面的观察是不够的。在真实的生产环境中,特别是在跨多个数据中心部署时,负载均衡策略的选择需要更加深入的理解和分析。

我们进一步分析了如何通过配置和自定义负载均衡规则来灵活应对各种场景。不论是局部配置还是全局配置,我们都能根据具体需求调整负载均衡的行为。同时,我们展示了如何通过自定义算法扩展Ribbon的负载均衡能力,以更好地适应特定业务场景的需求。

在接下来的章节中,我们将深入探讨OpenFeign组件。我们的重点将是如何使开发人员能够更多关注业务逻辑代码,而不是被迫处理与RPC调用相关的繁琐细节。


我是努力的小雨,一名 Java 服务端码农,潜心研究着 AI 技术的奥秘。我热爱技术交流与分享,对开源社区充满热情。同时也是一位掘金优秀作者、腾讯云内容共创官、阿里云专家博主、华为云云享专家。

标签:负载,算法,server,IRule,均衡,public
From: https://www.cnblogs.com/guoxiaoyu/p/18347224

相关文章

  • MetaLLM大语言模型文本生成算法分析报告
    一、算法安全与监测算法安全信息内容安全方面,MetaLLM算法必须确保生成的文本不包含有害信息,如不当言论、歧视性内容等。这需要在训练数据中进行严格的筛选,并在模型设计时加入过滤机制。信息源安全则关注于训练数据的质量和多样性,以防止偏差和误解。算法监测信息安全监测:持......
  • 商汤AI代码生成算法分析报告
    1.算法安全与监测信息内容安全商汤AI代码生成算法在处理用户输入时,必须确保数据内容的保密性和完整性。由于算法涉及敏感的编程信息,任何未授权的访问或数据泄露都可能导致严重的安全问题。因此,算法应采用加密传输和存储机制来保护数据。信息源安全算法需要验证用户输入的......
  • 基于模糊控制算法的倒立摆控制系统matlab仿真
    1.课题概述       基于模糊控制算法的倒立摆控制系统,模糊规则,模糊控制器等通过MATLAB编程实现,通过模糊控制器对小车倒立摆平衡系统进行控制,输出倒立摆从不稳定到稳定的动画过程,最后输出小车,倒立摆的收敛过程。 2.系统仿真结果   3.核心程序与模型版本:MAT......
  • Unity中利用遗传算法训练MLP
    Unity中利用遗传算法训练MLP梯度下降法训练神经网络通常需要我们给定训练的输入-输出数据,而用遗传算法会便捷很多,它不需要我们给定好数据,只需要随机化多个权重进行N次“繁衍进化”,就可以得出效果不错的网络。这种训练方式的好处就是不需要训练用的预期输出数据,适合那类可以简单......
  • (算法)猜数字⼤⼩II————<暴搜->记忆化搜索->动态规划>
    1.题⽬链接:375.猜数字⼤⼩II2.题⽬描述:3.解法(暴搜->记忆化搜索):算法思路:暴搜:a.递归含义:给dfs⼀个使命,给他⼀个区间[left,right],返回在这个区间上能完胜的最⼩费⽤;b.函数体:选择[left,right]区间上的任意⼀个数作为头结点,然后递归分析左右⼦树。求出所有情况......
  • (算法)最⻓递增⼦序列————<暴搜->记忆化搜索->动态规划>
    1.题⽬链接:300.最⻓递增⼦序列2.题⽬描述:3.解法(暴搜->记忆化搜索->动态规划):算法思路:暴搜:a.递归含义:给dfs⼀个使命,给他⼀个数i,返回以i位置为起点的最⻓递增⼦序列的⻓度;b.函数体:遍历i后⾯的所有位置,看看谁能加到i这个元素的后⾯。统计所有情况下的最⼤值。......
  • 代码随想录算法训练营第二十八天 | 122.买卖股票的最佳时机II , 55. 跳跃游戏 , 45.跳跃
    目录122.买卖股票的最佳时机II 思路方法一:贪心方法二:动态规划55.跳跃游戏思路方法一:使用while循环方法二:使用for循环45.跳跃游戏II 思路方法一方法二方法一:贪心方法一方法二:贪心方法二 方法三:贪心方法三心得体会1005.K次取反后最大化的数组和思路方法......
  • 数学:素性测试算法
    算法简介对一个数的素性测试有很多种做法,有确定性测试的算法,也有概率性测试的算法。确定性素性测试算法确定性素性测试这里介绍两种:线性筛法:利用线性筛在\(O(n)\)的时间复杂度内,将一个范围内的数素性全部求出,然后\(O(1)\)查询。试除法:在\(\sqrt{n}\)内试商,判定是否......
  • 代码随想录算法训练营第十四天(一)| 226.翻转二叉树 101. 对称二叉树
    226.翻转二叉树题目:给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。示例1:输入:root=[4,2,7,1,3,6,9]输出:[4,7,2,9,6,3,1]示例2:输入:root=[2,1,3]输出:[2,3,1]示例3:输入:root=[]输出:[]提示:树中节点数目范围在 [0,100] 内-100<=......
  • Java数组06:常见排序算法
    1.冒泡排序冒泡排序(BubbleSort),是一种计算机科学领域的较简单的排序算法。它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完......