首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

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

编程知识
2024年08月14日 08:23

上一期我们详细探讨了微服务之间的通信,特别是介绍了如何集成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 技术的奥秘。我热爱技术交流与分享,对开源社区充满热情。同时也是一位掘金优秀作者、腾讯云内容共创官、阿里云专家博主、华为云云享专家。

💡 我将不吝分享我在技术道路上的个人探索与经验,希望能为你的学习与成长带来一些启发与帮助。

🌟 欢迎关注努力的小雨!🌟

From:https://www.cnblogs.com/guoxiaoyu/p/18347224
本文地址: http://shuzixingkong.net/article/1081
0评论
提交 加载更多评论
其他文章 神经网络之卷积篇:详解卷积步长(Strided convolutions)
详解卷积步长 卷积中的步幅是另一个构建卷积神经网络的基本操作,让向展示一个例子。 如果想用3&#215;3的过滤器卷积这个7&#215;7的图像,和之前不同的是,把步幅设置成了2。还和之前一样取左上方的3&#215;3区域的元素的乘积,再加起来,最后结果为91。 只是之前移动蓝框的步长是1,现在移动
神经网络之卷积篇:详解卷积步长(Strided convolutions) 神经网络之卷积篇:详解卷积步长(Strided convolutions) 神经网络之卷积篇:详解卷积步长(Strided convolutions)
数据结构之链表超详解(1)
一人 我饮酒醉醉把佳人成双对两眼 是独相随只求他日能双归娇女 我轻扶琴燕嬉 我紫竹林痴情红颜 心甘情愿千里把你寻说红颜 我痴情笑曲动 我琴声妙轻狂高傲 懵懂无知只怪太年少弃江山 我忘天下斩断情丝无牵挂千古留名传佳话我两年征战已白发一生征战何人陪谁是谁非谁相随戎马一生为了谁我能爱几回恨几回败帝王 斗苍
数据结构之链表超详解(1) 数据结构之链表超详解(1) 数据结构之链表超详解(1)
【动画进阶】神奇的卡片 Hover 效果与 Blur 的特性探究
本文,我们将一起探讨探讨,如下所示的一个卡片 Hover 动画,应该如何实现: 这个效果的几个难点: 鼠标移动的过程中,展示当前卡片边缘的 border 以及发光效果; 效果只出现在鼠标附近?这一块的实现方法就有很多种了,可以计算鼠标附近的范围,在范围内去实现的效果,但是这样成本太高了。 转换一下思
【动画进阶】神奇的卡片 Hover 效果与 Blur 的特性探究 【动画进阶】神奇的卡片 Hover 效果与 Blur 的特性探究 【动画进阶】神奇的卡片 Hover 效果与 Blur 的特性探究
StarNet:关于 Element-wise Multiplication 的高性能解释研究 | CVPR 2024
论文揭示了star operation(元素乘法)在无需加宽网络下,将输入映射到高维非线性特征空间的能力。基于此提出了StarNet,在紧凑的网络结构和较低的能耗下展示了令人印象深刻的性能和低延迟 来源:晓飞的算法工程笔记 公众号 论文: Rewrite the Stars 论文地址:https:/
StarNet:关于 Element-wise Multiplication 的高性能解释研究 | CVPR 2024 StarNet:关于 Element-wise Multiplication 的高性能解释研究 | CVPR 2024 StarNet:关于 Element-wise Multiplication 的高性能解释研究 | CVPR 2024
SpringBoot 整合线程池
分为三步 启动类加 @EnableAsync 注解 在方法上加 @Async 注解 创建线程池配置类 1.启动类加 @EnableAsync 注解 @SpringBootApplication @EnableAsync public class FacadeH5Application { publi
聊一聊 Netty 数据搬运工 ByteBuf 体系的设计与实现
本文基于 Netty 4.1.56.Final 版本进行讨论 时光芿苒,岁月如梭,好久没有给大家更新 Netty 相关的文章了,在断更 Netty 的这段日子里,笔者一直在持续更新 Linux 内存管理相关的文章 ,目前为止,算是将 Linux 内存管理子系统相关的主干源码较为完整的给大家呈现了出来
聊一聊 Netty 数据搬运工 ByteBuf 体系的设计与实现 聊一聊 Netty 数据搬运工 ByteBuf 体系的设计与实现 聊一聊 Netty 数据搬运工 ByteBuf 体系的设计与实现
.NET 8 中利用 MediatR 实现高效消息传递
前言 MediatR 是 .NET 下的一个实现消息传递的库,轻量级、简洁高效,用于实现进程内的消息传递机制。它基于中介者设计模式,支持请求/响应、命令、查询、通知和事件等多种消息传递模式。通过泛型支持,MediatR 可以智能地调度不同类型的消息,非常适合用于领域事件处理。 在本文中,将通过一个简
.NET 8 中利用 MediatR 实现高效消息传递 .NET 8 中利用 MediatR 实现高效消息传递 .NET 8 中利用 MediatR 实现高效消息传递
Sy.ExpressionBuilder 动态查询新体验
省流模式,看下对比 //常规查询 var query = users .WhereIf(m =&gt; m.UserName.Contains(input.UserName), !string.IsNullOrEmpty(input.UserName)) .WhereIf(m =&gt; input
Sy.ExpressionBuilder 动态查询新体验 Sy.ExpressionBuilder 动态查询新体验 Sy.ExpressionBuilder 动态查询新体验