java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringCloud LoadBalancer负载均衡策略与缓存机制

SpringCloud LoadBalancer负载均衡策略与缓存机制详解

作者:Master_hl

这篇文章主要介绍了SpringCloud LoadBalancer负载均衡策略与缓存机制详解,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

1. 什么是 LoadBalancer ?

LoadBalancer(负载均衡器)是一种用来分发网络或应用程序流量到多个服务器的技术。

它可以防止任何单一服务的过载,通过分散负载来保持整个系统的平稳运行,保证系统的高可用性和可靠性。

2. 负载均衡策略的分类

负载均衡策略大体上分为两类:服务端的负载均衡和客户端的负载均衡

① 服务端负载均衡 (如 Nginx、F5)

请求先到达一个中介(如负载均衡器设备或者服务,例如Nginx),由这个中介根据配置的策略将请求分发到后端的多个服务器中。它对客户端是透明的,即客户端不需要知道有多少服务器以及它们的存在

② 客户端负载均衡 (如 Ribbon、Spring Cloud LoadBalancer)

请求的分配逻辑由客户端持有,客户端直接决定将请求发送到哪一个服务器。也就是说在客户端负载均衡中,客户端通常具备一份服务列表,它知道每个服务的健康状况,基于这些信息和负载均衡策略,客户端会选择一个最适合的服务去发送请求。

服务端负载均衡和客户端负载均衡策略有什么区别 ?

它俩的区别主要在灵活性和性能两方面(结合上面两幅图来理解):

1. 灵活性

2. 性能

【扩充知识】

如果将负载均衡器视为代理,那么服务端负载均衡可以视作是反向代理的一种形式,因为它接收客户端请求后再决定将其分配给哪一个服务器;而客户端负载均衡则可以看作具有正向代理的性质,因为客户端知道要联系的服务列表,并直接向选定的服务器发送请求。

2.1 常见的负载均衡策略

常见的负载均衡策略有以下几种:

3. 为什么要学习 Spring Cloud Balancer ?

因为 Ribbon 作为早期的客户端负载均衡工具,在 Spring Cloud 2020.0.0 版本之后已经被移除了,取而代之的是 Spring Cloud LoadBalancer,而且 Ribbon 也已经不再维护,所以它也是 Spring 官方推荐的负载均衡解决方案。

其他一些原因:

4. Spring Cloud LoadBalancer 内置的两种负载均衡策略

4.1 轮询负载均衡策略(默认的)

从它的源码实现可以看出来默认的负载均衡策略是轮询的策略。

IDEA 搜索它的配置类 LoadBalancerClientConfiguration:

进入到 RoundRobinLoadBalancer 这个类里边,定位到 getInstanceResponse 方法,就能看到轮询策略的关键代码:

private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
    if (instances.isEmpty()) {
        if (log.isWarnEnabled()) {
            log.warn("No servers available for service: " + this.serviceId);
        }

        return new EmptyResponse();
    } else if (instances.size() == 1) {
        return new DefaultResponse((ServiceInstance)instances.get(0));
    } else {
        // 轮询策略的关键代码
        int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
        ServiceInstance instance = (ServiceInstance)instances.get(pos % instances.size());
        return new DefaultResponse(instance);
    }
}

理解关键代码:

int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
ServiceInstance instance = (ServiceInstance)
                instances.get(pos % instances.size()); // 进行轮询选择

4.2 随机负载均衡策略

实现随机负载均衡策略的步骤:

① 创建随机负载均衡策略

② 设置随机负载均衡策略

接下来的操作都是基于这篇博客基础上去操作的,有需要的可以先去看看这篇博客,先把前置的代码准备好:https://www.jb51.net/program/360525nc3.htm

4.2.1 创建随机负载均衡策略

这些写法都是相通的,可以仿照源码中的轮询策略的关键代码:

可以去源码中的LoadBalancerClientConfiguration中去定位到 reactorServiceInstanceLoadBalancer 方法,然后复制下来,修改几个关键地方即可。

public class RandomLoadBalancerConfig {

    // 随机的负载均衡策略
    @Bean
    public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty("loadbalancer.client.name");
        return new RandomLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name,
                        ServiceInstanceListSupplier.class), name);
    }
}

4.2.2设置随机负载均衡策略

在 consumer 模块中的 service 接口上设置负载均衡策略:

@Service
@FeignClient("loadbalancer-service")
// 设置局部的负载均衡策略
@LoadBalancerClient(name = "loadbalancer-service",
    configuration = RandomLoadBalancerConfig.class)
public interface UserService {

    @RequestMapping("/user/getname")
    public String getName(@RequestParam("id") Integer id);
}

PS:有时候局部的负载均衡策略不会生效(版本问题),可以将其设为全局的负载均衡策略。

如何设置全局的负载均衡策略:(在启动类上加 @LoadBalancerClients 注解)

@SpringBootApplication
@EnableFeignClients  // 开启 OpenFeign
// 设置全局的负载均衡策略
@LoadBalancerClients(defaultConfiguration =
    RandomLoadBalancerConfig.class)
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

}

这个时候,就是随机的负载均衡策略了,可以启动两个生产者和消费者,然后拿着消费者这边的端口去获取服务感受。

5. Nacos 权重负载均衡器

Nacos 中有两种负载均衡策略:权重负载均衡策略和 CMDB(地域就近访问)标签负载均衡策略

它默认的策略是权重。

在 Spring Cloud Balancer 配置为 Nacos 负载均衡器的步骤:

① 创建 Nacos 负载均衡器

② 设置 Nacos 负载均衡器

5.1 创建 Nacos 负载均衡器

配置 Nacos 负载均衡需要注入NacosDiscoveryProperties 这个类,因为它需要使用到配置文件中的一些关键信息。

@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerConfig.class)
public class NacosLoadBalancerConfig {
    @Resource
    NacosDiscoveryProperties nacosDiscoveryProperties;

    @Bean
    public ReactorLoadBalancer<ServiceInstance> nacosLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty("loadbalancer.client.name");
        return new NacosLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name,
                        ServiceInstanceListSupplier.class), name, nacosDiscoveryProperties);
    }
}

5.2设置 Nacos 负载均衡器

@Service
@FeignClient("loadbalancer-service")
// 设置局部的负载均衡策略
@LoadBalancerClient(name = "loadbalancer-service",
    configuration = NacosLoadBalancerConfig.class)
public interface UserService {

    @RequestMapping("/user/getname")
    public String getName(@RequestParam("id") Integer id);
}

再测试之前,可以先将 Nacos 中一个生产者的权重给设置为 10,一个设置为 1,这样就能明显感受到 Nacos 权重的负载均衡策略了。

6. 自定义负载均衡器

自定义负载均衡策略需要 3 个步骤:

① 创建自定义负载均衡器

② 封装自定义负载均衡器

③ 为服务设置自定义负载均衡策器

6.1创建自定义负载均衡器

这里也是可以参考源码的实现的,搜索RandomLoadBalancer 这个类,模仿它的实现去创建自定义负载均衡器。

Ⅰ. 创建一个负载均衡类,并让其实现ReactorServiceInstanceLoadBalancer 接口;

Ⅱ. 复制RandomLoadBalancer 的整个方法体,粘贴到自定义负载均衡类中,并修改构造方法名称

Ⅲ. 在关键方法 getInstanceResponse 中实现自定义负载均衡策略(以IP哈希负载均衡为例)

public class CustomLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    private static final Log log = LogFactory.getLog(CustomLoadBalancer.class);
    private final String serviceId;
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public CustomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }

    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next().map((serviceInstances) -> {
            return this.processInstanceResponse(supplier, serviceInstances);
        });
    }

    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
        Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());
        }

        return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: " + this.serviceId);
            }

            return new EmptyResponse();
        } else {
            // 自定义负载均衡策略
            
            // 获取 Request 对象
            ServletRequestAttributes attributes = (ServletRequestAttributes)
                    RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            String ipAddress = request.getRemoteAddr();
            System.out.println("用户 IP:" + ipAddress);
            int hash = ipAddress.hashCode();
            // IP 哈希负载均衡【关键代码】
            int index = hash % instances.size();
            // 得到服务实例的方法
            ServiceInstance instance = (ServiceInstance) instances.get(index);
            return new DefaultResponse(instance);
        }
    }
}

6.2封装自定义负载均衡器

public class CustomLoadBalancerConfig {
    // IP 哈希负载均衡
    @Bean
    public ReactorLoadBalancer<ServiceInstance> customLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty("loadbalancer.client.name");
        return new CustomLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name,
                        ServiceInstanceListSupplier.class), name);
    }
}

6.3为服务设置自定义负载均衡策器

@Service
@FeignClient("loadbalancer-service")
// 设置局部的负载均衡策略
@LoadBalancerClient(name = "loadbalancer-service",
    configuration = CustomLoadBalancerConfig.class)
public interface UserService {

    @RequestMapping("/user/getname")
    public String getName(@RequestParam("id") Integer id);
}

PS:测试的时候发现自定义的负载均衡策略不生效怎么办 ?

① 把前边的 Nacos 的负载均衡器一整个注释掉(包括 @LoadBalancerClients注解),只提供一个类。

② 如果设置局部的负载均衡不生效,就去启动类上设置全局的负载均衡策略。

@SpringBootApplication
@EnableFeignClients  // 开启 OpenFeign
// 设置全局的负载均衡策略
@LoadBalancerClients(defaultConfiguration =
    CustomLoadBalancerConfig.class)
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

}

7. Spring Cloud LoadBalancer 中的缓存机制

Spring Cloud LoadBalancer 中获取服务实例有两种方式:

1. 实时获取:每次都从注册中心得到最新的健康实例(效果好,开销大)

2. 缓存服务列表:每次得到服务列表之后,缓存一段时间(既保证性能,也能保证一定的及时性)

Spring Cloud LoadBalancer 默认开启了缓存服务列表的功能。

测试 Spring Cloud LoadBalancer 的缓存机制:

【测试结果】 当我下线第一个服务的时候,立马去获取服务,这个时候还是两个服务轮询的获取,等过了 35s 左右,就只能获取到 64067 这个服务了。

7.1 Spring Cloud LoadBalancer 中缓存机制的一些特性

默认特性如下:

① 缓存的过期时间为 35s;

② 缓存保存个数为 256 个。

我们可以通过在配置文件中去设置这些特性:

spring:
  cloud:
    loadbalancer:
      cache:
        ttl: 35s  # 过期时间
        capacity: 1024  # 设置缓存个数

7.2 关闭缓存

关闭 Spring Cloud LoadBalancer 中的缓存可以通过以下配置文件来设置:

spring:
  cloud:
    loadbalancer:
      cache:
        enabled: false  # 关闭缓存

PS:尽管关闭缓存对于开发和测试很有用,但是在生产环境上,它的效率是要远低于开启缓存,所以在生产环境上始终都要开启缓存。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文