Lecture06-SpringCloud-负载均衡LoadBalancer

文章目录

    [toc]

    单词词性音标含义
    load balancern./ləʊd ˈbælənsə(r)/负载均衡(把请求分散到多个实例,避免某个实例过载)
    strategyn./ˈstrætədʒi/策略(负载均衡选实例的规则:轮询、随机、权重…)
    weightn./v./weɪt/权重(按比例分配流量的依据,值越大分到越多)
    round-robinadj./raʊnd ˈrɒbɪn/轮询(轮流把请求分给每个实例)
    stickyadj./ˈstɪki/粘性的(同一请求尽量固定到同一实例,便于保持会话)
    degradev./dɪˈɡreɪd/降级(某实例不可用时,自动剔除并继续用其他实例)

    Lecture06-SpringCloud-负载均衡LoadBalancer

    目标:理解客户端负载均衡原理,掌握 Spring Cloud LoadBalancer 的内置策略与指定方式(轮询 / 随机 / Nacos 权重),以及如何给 Feign 调用分配多个实例。

    1、什么是负载均衡

    同一个服务如果只有一个实例,那么一旦宕机就无法对外提供服务,所以基本上都需要搭建集群,配套多个实例,避免单点故障。

    另外,搭建集群还能够让各个实例分摊负载,避免单个实例被海量请求压垮。

    那么对消费端来说,每一次请求都需要决定具体访问哪一个实例。

    在注册中心有详细的实例列表,由负载均衡组件决定具体调用哪一个。

    OpenFeign 需要做的是给负载均衡组件提供目标服务的名称。

    user-service 有 3 个实例:127.0.0.1:8081 / 8082 / 8083
    调用方每次发起 /user/{id} 请求,负载均衡选一个实例
    

    loadbalancer

    两种负载均衡:

    • 服务端:由独立负载均衡器(如 Nginx)分发,客户端只连一个入口。
    • 客户端(本课程):调用方内置选实例逻辑(Spring Cloud LoadBalancer),配合 Nacos 发现实例。

    衔接:第 03 讲我们手写"取第一个实例",第 05 讲 OpenFeign 默认就用了 LoadBalancer——这就是本讲要讲清楚的那层逻辑。


    2、Ribbon 已退役,用 LoadBalancer

    早期 Spring Cloud 用 Netflix Ribbon 做客户端负载均衡,已停止维护。现用官方 Spring Cloud LoadBalancer。

    ⚠️ 它不是 OpenFeign 自动带进来的。官方文档明确写:spring-cloud-starter-openfeign 把 spring-cloud-starter-loadbalancer 列为 optional(可选) 依赖,不会传递给你。

    不加它会怎样?Feign 调 @FeignClient(name="user-service") 时,需要按"服务名"解析出实例地址——这一步就是 LoadBalancer 在做的。缺了它,启动或运行时会报找不到 FeignBlockingLoadBalancerClient 的错。所以只要用 Feign 调远程服务,这一行就得自己加:

    <!-- 必须显式加:openfeign 里它是 optional,不自动传递 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    

    3、默认负载均衡规则

    LoadBalancer 默认策略是 轮询(Round-Robin):多个实例轮流被选中,请求被均分。

    演示方式:给 user-service 开 3 个实例(8081/8082/8083,同一 jar 不同端口),用 Feign 连续调用多次,观察请求分别落到不同实例。

    第 1 次请求 → 8081
    第 2 次请求 → 8082
    第 3 次请求 → 8083
    第 4 次请求 → 8081   (回到起点,循环)
    

    验证手段:每个实例返回内容里带自己的端口。


    4、内置策略有哪些

    LoadBalancer 的选择逻辑分两层,先分清这两层,后面就不会混淆:

    层次负责什么接口
    算法层从候选实例里"挑"哪一个ReactorLoadBalancer
    筛选层先决定"哪些实例进入候选"ServiceInstanceListSupplier

    算法层:官方只提供了两个实现

    策略实现类说明
    轮询(默认)RoundRobinLoadBalancer多个实例轮流被选中,请求被均分
    随机RandomLoadBalancer每次从候选实例里随机挑一个

    筛选层:官方提供了多个开箱即用的预置配置(用配置属性切换,见下一节)

    预置值效果
    default(默认)只做基础发现 + 缓存
    weighted加权,按实例 metadata 里的 weight 值分配流量
    same-instance-preference尽量复用上一次选中的实例(替代 Zookeeper 的 StickyRule)
    request-based-sticky-session按请求 cookie 固定到同一实例
    zone-preference同区域(zone)的实例优先
    health-check定时探活,只返回健康实例
    subset只在一部分实例子集里选(压测、灰度常用)
    api-version按接口版本筛选实例

    另外,Spring Cloud Alibaba 还提供了 NacosLoadBalancer:它读的是 Nacos 控制台上的实例权重,并且同集群实例优先。它不属于 Spring Cloud 官方内置,需要单独开启。


    5、如何指定策略

    1、方式一:配置类切换算法(改"挑"的规则)

    RoundRobinLoadBalancer / RandomLoadBalancer 不能用配置文件切换(原因见本节的"常见误区"),要写配置类 + 注解。

    第 1 步:写配置类。注意:不要加 @Configuration,也不要放进主启动类的组件扫描范围。

    // src/main/java/com/atguigu/cloud/lb/CustomLoadBalancerConfiguration.java
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.client.loadbalancer.LoadBalancerClientFactory;
    import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
    import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
    import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.core.env.Environment;
    
    public class CustomLoadBalancerConfiguration {
    
        @Bean
        ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
                Environment environment,
                LoadBalancerClientFactory loadBalancerClientFactory) {
            // 当前正在为哪个服务做负载均衡(这里是 user-service)
            String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
            return new RandomLoadBalancer(
                    loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
                    name);
        }
    }
    

    第 2 步:挂到目标服务上

    // 只对 user-service 生效
    @Configuration
    @LoadBalancerClient(value = "user-service", configuration = CustomLoadBalancerConfiguration.class)
    public class LbClientConfig {
    }
    

    想让所有服务都用随机,改用 @LoadBalancerClients:

    // 对所有服务生效
    @Configuration
    @LoadBalancerClients(defaultConfiguration = CustomLoadBalancerConfiguration.class)
    public class LbGlobalConfig {
    }
    
    注解作用范围
    @LoadBalancerClient(value = "服务名", configuration = X.class)只对该服务生效
    @LoadBalancerClients(defaultConfiguration = X.class)全局默认

    2、方式二:配置属性切换预置配置(改"筛"的规则)

    筛选层的预置配置直接写配置文件即可,还能细到某个服务:

    spring:
      cloud:
        loadbalancer:
          configurations: weighted              # 全局:所有服务都按权重筛选
          # clients:
          #   user-service:
          #     configurations: health-check    # 只对 user-service 生效(按需开启)
    

    3、方式三:Nacos 权重(Spring Cloud Alibaba)

    生产上更常用的是"按机器性能分配流量"——新机器权重低一点,让它先"热身"。用 SCA 的 NacosLoadBalancer 实现:

    spring:
      cloud:
        loadbalancer:
          nacos:
            enabled: true          # 开启 NacosLoadBalancer(默认 false,即默认仍走轮询)
        nacos:
          discovery:
            weight: 1              # 本实例权重,1~100,越大分到的流量越多
    

    验证方式:把 user-service 起两个实例,权重分别设成 1 和 100,连续调用 20 次,观察绝大多数请求落到高权重实例上。

    # 实例 A(权重 1)
    java -jar user-service.jar --server.port=8081 --spring.cloud.nacos.discovery.weight=1
    # 实例 B(权重 100)
    java -jar user-service.jar --server.port=8082 --spring.cloud.nacos.discovery.weight=100
    

    4、常见误区:configurations=random 是错的

    网上不少文章写"把策略改成随机,只要配 spring.cloud.loadbalancer.configurations=random"——这个值根本不存在。

    查 LoadBalancerClientConfiguration 源码可知,configurations 只认这几个值:default、zone-preference、health-check、request-based-sticky-session、same-instance-preference、weighted、subset、api-version。

    原因正是前面那个两层结构:configurations 管的是筛选层(哪些实例进候选),而轮询/随机属于算法层,只能用配置类 + @LoadBalancerClient 替换。写错了不会报错,只会静默地继续用轮询——这正是它容易骗过人的地方。


    6、实例剔除与容错

    负载均衡会自动感知实例健康状态:

    • 通过 Nacos 心跳,不健康的实例不会出现在候选列表里。
    • 某个实例调用失败,LoadBalancer 可结合重试换下一个实例再试。
    spring:
      cloud:
        loadbalancer:
          cache:
            enabled: true          # 缓存实例列表,减少查询压力
    

    进阶:spring.cloud.loadbalancer.retry 配合超时,实现"失败自动重试其他实例"(可让 Feign + LoadBalancer 一起配置重试,生产常用)。


    7、课堂练习

    1. 把 user-service 用不同端口开 3 个实例,让 order-service 通过 Feign 连续调用 10 次。
    2. 观察轮询效果:请求是否被平均分到 3 个实例。
    3. 用配置类 + @LoadBalancerClient 把策略换成 RandomLoadBalancer,再调用 10 次,对比两次的分布差异。
    4. 给两个实例设置不同权重(1 和 100),并开启 spring.cloud.loadbalancer.nacos.enabled=true,观察流量是否向高权重实例倾斜。
    5. 停掉其中一个实例,观察剩余实例是否仍能正常响应(健康剔除)。

    8、本讲小结

    • 负载均衡解决"选哪个实例",客户端负载均衡由调用方内置。
    • Ribbon 退役,用 Spring Cloud LoadBalancer(必须自己显式加依赖——openfeign 把它列为 optional,不自动传递)。
    • 选择逻辑分两层:算法层(ReactorLoadBalancer:轮询 / 随机)与筛选层(ServiceInstanceListSupplier:权重 / 同实例优先 / 健康检查…)。
    • 算法层用配置类 + @LoadBalancerClient 替换;筛选层用 spring.cloud.loadbalancer.configurations 属性切换。
    • SCA 额外提供 NacosLoadBalancer(spring.cloud.loadbalancer.nacos.enabled=true),按 Nacos 权重 + 同集群优先。
    • 不健康实例自动剔除,结合重试实现容错。

    9、本节验证

    • 理解负载均衡的作用与常见策略(轮询/随机/权重/粘性)✅
    • 知道 Ribbon 已退役,用 Spring Cloud LoadBalancer,且它需要自己显式引入(openfeign 不传递)✅
    • 说得清算法层与筛选层的分工,会用配置类把轮询换成随机 ✅
    • 会切换预置配置(configurations)并用 Nacos 权重做流量倾斜 ✅
    • 知道 configurations=random 是误传,写了不会生效 ✅
    • 理解实例剔除与容错降级 ✅
    end