So easy! 教你实现自定义负载均衡策略!

简介: So easy! 教你实现自定义负载均衡策略!

前言

Feign 是⼀个 HTTP 请求的轻量级客户端框架。通过接口和注解的方式发起 HTTP 请求调用,面向接口编程,而不是像 Java 中通过封装 HTTP 请求报文的方式直接调用。服务消费方拿到服务提供方的接⼝,然后像调⽤本地接⼝⽅法⼀样去调⽤,实际发出的是远程的请求。

负载均衡是微服务架构中必须使用的技术,通过负载均衡来实现系统的高可用、集群扩容等功能。负载均衡可通过硬件设备及软件来实现,硬件比如:F5、Array 等,软件比如:LVS、Nginx 等。常用的负载均衡算法有:轮训、随机、加权轮训、加权随机、地址哈希等方法,负载均衡器维护一份服务列表,根据负载均衡算法将请求转发到相应的微服务上,所以负载均衡可以为微服务集群分担请求,降低系统的压力。

Feign 在调用 Http 接口时,需要先通过负载均衡策略获取一个服务实例后才能去调用微服务。在实践中,经常会遇到需要自定义负载均衡策略的场景,比如在本机开发调试时,需要优先调用本机或局域网内的微服务。

实现原理

Feign 通过动态代理,实现调用接口的方式发起 HTTP 请求调用,其中的核心方法见下图所示:

RetryableFeignBlockingLoadBalancerClient 内 execute 方法里,调用 BlockingLoadBalancerClient 类的 choose 方法获取服务实例,

该方逻辑是先根据 serviceId,即服务名获取对应的 ReactiveLoadBalancer 反应式负载均衡器,反应式负载均衡器根据请求信息 request, 阻塞获取最终的服务实例。

以 RoundRobinLoadBalancer 轮询负载均衡器为例,该类实现了 ReactiveLoadBalancer 接口,在 choose 方法,先获取 ServiceInstanceListSupplier 对象,该对象是服务列表的提供者,通过该对象获取过滤后的服务列表,然后对列表轮询计算选取最终的服务实例。

详细实现

根据上述原理,通过自定义实现 ServiceInstanceListSupplier 对象以及负载均衡器可以实现自定义策略。下面为具体的代码实现:

  1. EnableCustomLoadBalance.java 自定义开启负载均衡策略注解,在启动类 Application 类上添加即可开启。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EnableCustomLoadBalanceImportSelector.class})
public @interface EnableCustomLoadBalance {
    String rule() default "local-first";
}

rule 即要实现的策略规则,实现多种策略后,可以通过 rule 指定策略。

  1. EnableCustomLoadBalanceImportSelector.java

EnableCustomLoadBalanceImportSelector 可以根据当前运行环境,判断是否加载自定义负载均衡相关配置。比如本机优先策略只在生成环境生效,其它环境不会加载和生效相关配置。

@Order
public class EnableCustomLoadBalanceImportSelector extends SpringFactoryImportSelector<EnableCustomLoadBalance> {
    public EnableCustomLoadBalanceImportSelector() {
    }
    public String[] selectImports(AnnotationMetadata metadata) {
        Environment env = this.getEnvironment();
        // 从配置项com.xx.loadbalancer.ignoreNamespaces 获取忽略生效的nacos命名空间 例如:test, prod
        String namespaces = env.getProperty("com.xx.loadbalancer.ignoreNamespaces", "test,staging,prod");
        List<String> namespaceList = Arrays.asList(StringUtils.trimAllWhitespace(namespaces).split("[,]+"));
        List<String> imports = new ArrayList<>();
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(this.getAnnotationClass().getName(), true));
        // 测试、正式等非开发环境不支持自定义策略
        if (env instanceof ConfigurableEnvironment) {
            ConfigurableEnvironment configEnv = (ConfigurableEnvironment) env;
            String property = configEnv.getProperty("spring.cloud.nacos.discovery.namespace");
            if (!StringUtils.hasText(property) || namespaceList.contains(property)) {
                return new String[0];
            }
            if (Objects.nonNull(attributes)) {
                String rule = attributes.getString("rule");
                if (StringUtils.hasText(rule)) {
                    imports.add("com.xx.basic.springboot.starter.component.loadbalancer.LoadBalanceAutoConfiguration");
                    LinkedHashMap<String, Object> map = new LinkedHashMap();
                    map.put("spring.cloud.loadbalancer.rule", rule);
                    MapPropertySource propertySource = new MapPropertySource("springCloudLoadbalancer", map);
                    configEnv.getPropertySources().addLast(propertySource);
                }
            }
        }
        return imports.toArray(new String[0]);
    }
    @Override
    protected boolean isEnabled() {
        return false;
    }
    @Override
    protected boolean hasDefaultFactory() {
        return true;
    }
}
  1. LoadBalanceAutoConfiguration.java

用于自定义负载均衡自动配置生效。

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

自动加载 LoadBalanceConfig 配置类,并限制在 ReactorLoadBalancerClientAutoConfiguration 和 LoadBalancerBeanPostProcessorAutoConfiguration 生效后开始配置。

  1. LoadBalanceConfig.java

实例化自定义提供服务列表对象 ServiceInstanceListSupplier,通过@ConditionalOnProperty 注解现在只在 rule 为 local-first 时实例化。自定义 RoundRobinLoadBalancer 负载均衡器,指定为默认负载均衡器。

public class LoadBalanceConfig {
    @Bean
    @ConditionalOnProperty(value = "spring.cloud.loadbalancer.rule", havingValue = "local-first")
    public ServiceInstanceListSupplier serviceInstanceListSupplier(ConfigurableApplicationContext context) {
        ServiceInstanceListSupplier supplier = ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withCaching().build(context);
        return new LocalServiceInstanceListSupplier(supplier);
    }
    @Bean
    @ConditionalOnProperty(
            value = {"spring.cloud.loadbalancer.rule"}, matchIfMissing = false)
    public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, ServiceInstanceListSupplier serviceInstanceListSupplier) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RoundRobinLoadBalancer(new SimpleObjectProvider<>(serviceInstanceListSupplier), name);
    }
}
  1. LocalServiceInstanceListSupplier.java

自定义 ServiceInstanceListSupplier 接口实现,

@Slf4j
public class LocalServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier {
    private InetUtils inetUtils;
    public LocalServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) {
        super(delegate);
        inetUtils = new InetUtils(new InetUtilsProperties());
    }
    @Override
    public Flux<List<ServiceInstance>> get() {
        return getDelegate().get().map(this::filterByLocalIp);
    }
    private List<ServiceInstance> filterByLocalIp(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 (resourceIp.equals(instance.getHost())) {
                targetList.add(instance);
            }
        }
        if (CollectionUtils.isEmpty(targetList)) {
            return instances;
        }
        return targetList;
    }
}

通过判断服务实例的 ip 是否与本机一致,筛选服务实例列表,提供给轮询负载均衡器 RoundRobinLoadBalancer。

  1. NacosAutoConfiguration

为了实现更灵活更负载的负载均衡策略,可以将服务实例的更多信息注册到 metadata 元数据中,这样 DelegatingServiceInstanceListSupplier 可以通过获取服务实例的 metadata 信息制定策略。本文以 nacos 为例,在服务启动时将启动时间和本机 hostname 注册到 metadata。

@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
@Slf4j
@AutoConfigureBefore({NacosDiscoveryAutoConfiguration.class})
public class NacosAutoConfiguration {
    public NacosAutoConfiguration() {
    }
    @Bean
    public NacosDiscoveryProperties myNacosProperties() {
        NacosDiscoveryProperties nacosDiscoveryProperties = new NacosDiscoveryProperties();
        nacosDiscoveryProperties.getMetadata().put("startup.time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        try (InetUtils inetUtils = new InetUtils(new InetUtilsProperties())) {
            InetUtils.HostInfo host = inetUtils.findFirstNonLoopbackHostInfo();
            nacosDiscoveryProperties.getMetadata().put("hostname", Optional.ofNullable(host).map(InetUtils.HostInfo::getHostname).orElse(""));
        }
        return nacosDiscoveryProperties;
    }
}

小结

通过上面原理讲解和实现代码,可以实现灵活的自定义负载均衡策略。

关键在于理解 Feign 的工作原理,以及 SpringCloud 是如何提供上层抽象的服务注册发现编程模型,屏蔽不同底层框架的具体实现。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
相关文章
|
11月前
|
缓存 负载均衡 网络协议
电商API接口性能优化技术揭秘:缓存策略与负载均衡详解
电商API接口性能优化是提升系统稳定性和用户体验的关键。本文聚焦缓存策略与负载均衡两大核心,详解其在电商业务中的实践。缓存策略涵盖本地、分布式及CDN缓存,通过全量或部分缓存设计和一致性维护,减少后端压力;负载均衡则利用反向代理、DNS轮询等技术,结合动态调整与冗余部署,提高吞吐量与可用性。文中引用大型及跨境电商平台案例,展示优化效果,强调持续监控与迭代的重要性,为电商企业提供了切实可行的性能优化路径。
|
11月前
|
人工智能 负载均衡 Cloud Native
云原生之负载均衡策略
ai必学之负载均衡 @[TOC]轮询处理;weight权重;ip_hash
|
负载均衡 算法
架构学习:7种负载均衡算法策略
四层负载均衡包括数据链路层、网络层和应用层负载均衡。数据链路层通过修改MAC地址转发帧;网络层通过改变IP地址实现数据包转发;应用层有多种策略,如轮循、权重轮循、随机、权重随机、一致性哈希、响应速度和最少连接数均衡,确保请求合理分配到服务器,提升性能与稳定性。
2933 11
架构学习:7种负载均衡算法策略
|
负载均衡 IDE Java
SpringBoot整合XXL-JOB【04】- 以GLUE模式运行与执行器负载均衡策略
在本节中,我们将介绍XXL-JOB的GLUE模式和集群模式下的路由策略。GLUE模式允许直接在线上改造方法为定时任务,无需重新部署。通过一个测试方法,展示了如何在调度中心配置并使用GLUE模式执行定时任务。接着,我们探讨了多实例环境下的负载均衡策略,确保任务不会重复执行,并可通过修改路由策略(如轮训)实现任务在多个实例间的均衡分配。最后,总结了GLUE模式和负载均衡策略的应用,帮助读者更深入理解XXL-JOB的使用。
979 9
SpringBoot整合XXL-JOB【04】-  以GLUE模式运行与执行器负载均衡策略
|
负载均衡
slb自定义健康检查路径
slb自定义健康检查路径
320 3
|
负载均衡 应用服务中间件 nginx
Nginx的6大负载均衡策略及权重轮询手写配置
【10月更文挑战第9天】 Nginx是一款高性能的HTTP服务器和反向代理服务器,它在处理大量并发请求时表现出色。Nginx的负载均衡功能可以将请求分发到多个服务器,提高网站的吞吐量和可靠性。以下是Nginx支持的6大负载均衡策略:
1148 7
|
负载均衡 算法 Java
腾讯面试:说说6大Nginx负载均衡?手写一下权重轮询策略?
尼恩,一位资深架构师,分享了关于负载均衡及其策略的深入解析,特别是基于权重的负载均衡策略。文章不仅介绍了Nginx的五大负载均衡策略,如轮询、加权轮询、IP哈希、最少连接数等,还提供了手写加权轮询算法的Java实现示例。通过这些内容,尼恩帮助读者系统化理解负载均衡技术,提升面试竞争力,实现技术上的“肌肉展示”。此外,他还提供了丰富的技术资料和面试指导,助力求职者在大厂面试中脱颖而出。
腾讯面试:说说6大Nginx负载均衡?手写一下权重轮询策略?
|
负载均衡 Java Nacos
常见的Ribbon/Spring LoadBalancer的负载均衡策略
自SpringCloud 2020版起,Ribbon被弃用,转而使用Spring Cloud LoadBalancer。Ribbon支持轮询、随机、加权响应时间和重试等负载均衡策略;而Spring Cloud LoadBalancer则提供轮询、随机及Nacos负载均衡策略,基于Reactor实现,更高效灵活。
1005 0
|
负载均衡 Java 对象存储
负载均衡策略:Spring Cloud与Netflix OSS的最佳实践
负载均衡策略:Spring Cloud与Netflix OSS的最佳实践
337 2
|
负载均衡 应用服务中间件 Linux
在Linux中,Nginx如何实现负载均衡分发策略?
在Linux中,Nginx如何实现负载均衡分发策略?

相关实验场景

更多