专业的编程技术博客社区

网站首页 > 博客文章 正文

Loadbalancer自定义基于相同网段优先的客户端负载均衡

baijin 2024-09-20 12:33:02 博客文章 3 ℃ 0 评论

引言

Hello 大家好,这里是Anyin。

在上篇 Spring Cloud Loadbalancer 工作流程[1] 我们知道了Spring Cloud Loadbalancer 的基本工作流程,今天我们来实现一个基于相同网段优先的客户端负载均衡器。

需求分析

在实现基于相同网段优先的自定义客户端负载均衡器之前,我们先说下为什么需要实现这个需求。

在我们日常的微服务应用开发中,如果一个系统有5个微服务应用,那么在正常情况下都会在线上的开发环境部署5个微服务应用。当开发人员在对某个微服务A进行迭代开发的时候,需要在本地启动该服务,这样子会导致在注册中心(这里以Nacos为例)会有2个微服务A实例,一个是线上的开发环境,一个是开发人员本地。如下图:

这样子会导致什么问题呢?

问题一

当另外一个微服务B依赖了微服务A,它在调用微服务A的时候,从注册中心会拿到微服务A的2个实例:一个线上开发环境,一个开发人员本地。因为默认的客户端负载均衡模式是轮训,这个时候微服务B会从这2个实例进行轮训的调用,当轮训到开发人员本地的实例的时候,会因为网络无法访问问题导致调用失败。

问题二

当另外一个开发人员开发微服务C的时候,它依赖了微服务A,需要和上一个开发人员进行联调。那这个时候因为因为微服务A有2个实例并且默认的负载均衡模式是轮训,这样子会导致微服务C会时不时的调用到线上开发环境的微服务A的实例,给开发带来了很大的不便。

代码实现

在代码实现的前提还有一个条件是需要线上开发环境网络的网段和办公环境网络的网段是不一样的,例如我们线上开发环境是k8s集群部署,pod的IP网段是10开头,而办公环境网络的网段一般是192开头。

基于这个前提,我们为了解决以上2个问题,我们来实现一个基于相同网段优先的客户端负载均衡器,即当调用方的网段和被调用方的网段一致,则优先进行客户端负载均衡。

Spring Cloud Loadbalancer 组件给我们开放的扩展点就是LoadBalancerClients注解,通过该注解的defaultConfiguration属性,我们可以注入我们的自定义配置。

所以,这里我们新增一个LoadBalanceAutoConfiguration类,然后通过LoadBalancerClients注解配置自定义配置类,代码如下:

@Configuration(proxyBeanMethods = false)
@LoadBalancerClients(defaultConfiguration = LoadBalanceConfig.class)
@AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class,
        LoadBalancerBeanPostProcessorAutoConfiguration.class })
public class LoadBalanceAutoConfiguration {
}

LoadBalanceConfig就是我们自定义的配置类,它注册了2个Bean实例:ServiceInstanceListSupplier(服务实例列表提供者) 和 ReactorLoadBalancer(负载均衡器),代码如下:

@Configuration(proxyBeanMethods = false)
public class LoadBalanceConfig {


    @Bean
    @ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "net-segment")
    public ServiceInstanceListSupplier serviceInstanceListSupplier(ConfigurableApplicationContext context) {
        ServiceInstanceListSupplier supplier = ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withCaching().build(context);
        return new NetSegmentServiceInstanceListSupplier(supplier);
    }


    @Bean
    public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, ServiceInstanceListSupplier serviceInstanceListSupplier) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RoundRobinLoadBalancer(new SimpleObjectProvider<>(serviceInstanceListSupplier), name);
    }
}

? 我们添加了一个配置属性:spring.cloud.loadbalancer.configurations,只有该属性为net-segment 才会实例化我们自定义的服务实例列表提供者。

? 我们自己手动注册了ReactorLoadBalancer的实例,不用默认的方式进行注册

最后,我们看看基于相同网段优先的服务实例列表提供者的实现

@Slf4j
public class NetSegmentServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier {


    private InetUtils inetUtils;


    public NetSegmentServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) {
        super(delegate);
        inetUtils = new InetUtils(new InetUtilsProperties());
    }


    @Override
    public Flux<List<ServiceInstance>> get() {
        return getDelegate().get().map(this::filterByNetSegment);
    }


    private List<ServiceInstance> filterByNetSegment(List<ServiceInstance> instances) {
        InetAddress host = inetUtils.findFirstNonLoopbackAddress();
        if(host == null){
            return instances;
        }
        String resourceIp = host.getHostAddress();
        List<ServiceInstance> targetList = Lists.newArrayList();
        for(ServiceInstance instance : instances){
            if(IPV4Util.isSameAddress(resourceIp, instance.getHost())){
                targetList.add(instance);
            }
        }
        if(CollectionUtil.isEmpty(targetList)){
            return instances;
        }
        return targetList;
    }
}

? InetUtils 通过该类的实例来获取本地IP,这个类也是DiscoveryClient实例在获取本机IP的方式。

? 通过IPV4Util工具类,判断本地IP网段是否和目标实例网段是否一致,如果一致,则添加到列表中。

? 最后如果发现没有相同网段的实例,则原样返回,避免报错。

最后

以上就实现了一个基于相同网段优先的客户端负载均衡了。你学会了吗?

相关源码地址: Anyin-Cloud[2]

References

[1] Spring Cloud Loadbalancer 工作流程: https://juejin.cn/post/7046298083218423845
[2] Anyin-Cloud:
https://gitee.com/anyin/anyin-cloud

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表