概述集群属性配好了实例也按机房分开了但order-service调user-service时照样跨机房——因为默认的负载均衡规则根本不认识 Nacos 的集群概念得把规则换成NacosRule。纲要承接服务分级存储模型服务 → 集群 → 实例为什么默认规则会跨机房ZoneAvoidanceRule的zone跟 Nacos 的cluster不是一回事NacosRule的选取逻辑同集群优先 → 集群内随机 → 都没有才跨集群附决策流程图跨集群告警日志格式、含义、运维价值配置实战真实application.yml为什么前缀是被调用方服务名为什么写在消费者侧完整可复现的验证流程造两个集群、连调五次、停掉本地集群、观察告警权重配置权重如何影响选择、权重为 0 意味着什么与 Ribbon 自带策略的关系以及 Spring Cloud Alibaba 新版本的写法变化五个容易踩的坑先理清默认规则为什么不认集群上一篇把user-service的三个实例分成了两拨8081、8082挂在HZ集群8083挂在SH集群。order-service也贴上了cluster-nameHZ。按直觉order-service应该只调8081和8082。实测却是三个实例轮流被打到——它还在轮询。原因在于负载均衡规则的默认实现。order-service用的是 Spring Cloud Netflix Ribbon默认 rule 是ZoneAvoidanceRule维度ZoneAvoidanceRule默认NacosRuleNacos 提供区域概念取 Eureka 实例的zone元数据eureka.instance.metadata-map.zone取 Nacos 实例的clusterNamespring.cloud.nacos.discovery.cluster-name数据来源Eureka 的 zoneNacos 注册中心的集群属性我们配了什么没配 zone所有实例 zone 相同 → 退化成轮询—同集群优先不支持支持集群内选择方式轮询随机跨集群行为无告警打 WARN 日志关键点ZoneAvoidanceRule眼里的区域来自 Eureka 的 zone 元数据。我们的工程早就把注册中心换成 Nacos 了Nacos 实例上报的是clusterName两个字段对不上ZoneAvoidanceRule拿到一片空白的 zone 信息只能退化成轮询。所以不是配置写错了是规则选错了。同集群优先这件事得用 Nacos 自己实现的NacosRule。NacosRule 的选取逻辑NacosRule是 Spring Cloud Alibaba 提供的IRule实现包名com.alibaba.cloud.nacos.ribbon.NacosRule。它挑实例分三步是否getInstances(serviceId)拿到服务全部实例过滤掉权重为 0 的实例存在与调用方同集群的健康实例?在同集群实例中按随机方式挑一个打印跨集群访问告警日志WARN 级别在剩余全部实例中按随机方式挑一个返回 Instance发起 HTTP 调用三条要记住优先同集群。从跟自己cluster-name相同的实例里挑。这是它存在的唯一理由。集群内是随机不是轮询。这一点视频里专门对比过连着请求五次被调用的实例顺序是8081 → 8082 → 8082 → 8081 → 8082这种来回跳的序列没有轮询那种整齐的循环。判断是不是轮询最直接的办法就是数次数看规律轮询会严格 1-2-3-1-2-3。本地没有才跨集群。只有当同集群内一个健康实例都没有时才会去别的集群找并且会打一条 WARN 日志。跨集群的那条告警别当成噪音本地集群全挂时order-service的日志里会出现这样一行不同版本实例信息串会有差异主干message一致2021-07-13 23:48:12.316 WARN 21940 --- [nio-8088-exec-3] c.a.c.nacos.ribbon.NacosRule : A cross-cluster call occursname userservice, clusterName HZ, instance Instance{instanceId127.0.0.1#8083#SH#DEFAULT#DEFAULT_GROUPuserservice, ip 127.0.0.1, port 8083}拆开看信息量很足name userservice被调用的服务名clusterName HZ调用方所在的集群也就是我想去哪instance ...#8083#SH#...实际打到的实例SH就是它真实所在的集群也就是说这行日志完整记录了我本该在杭州找用户服务但杭州没人只好打到了上海的 8083。它的价值在于跨机房调用是延迟上涨、带宽成本上升的前兆而这条日志是它最早的信号。正常情况下服务不该跨机房一旦出现告警运维该做的动作是去查HZ集群里的实例为什么掉线——是进程崩了、健康检查没通过还是发布时集体下线了。不配这条告警就看不到业务只会表现为接口慢了一点很难定位。顺带说一句本地集群没了但还能正常返回说明 Nacos 的可用性设计是对的——本地不可用不影响功能只影响性能。这也是集群划分能落地的原因。配置实战两处改动都在消费者侧。给 order-service 配集群order-service/src/main/resources/application.ymlspring:cloud:nacos:server-addr:localhost:8848discovery:cluster-name:HZ# 集群名称cluster-name的值要和本地机房保持一致这里是HZ否则NacosRule会把自己当成没有同集群实例直接走跨集群分支。换掉负载均衡规则这是本篇的核心配置来自终态工程的order-service/src/main/resources/application.yml原样引用userservice:ribbon:NFLoadBalancerRuleClassName:com.alibaba.cloud.nacos.ribbon.NacosRule# 负载均衡规则两个地方容易被问前缀为什么是userservice因为负载均衡规则是按被调用方配的。userservice是被调用的服务名ribbon.NFLoadBalancerRuleClassName是 Ribbon 约定的配置键。写在userservice下面意思是我调userservice时用这个规则。所以我完全可以给另一个服务配别的规则userservice:ribbon:NFLoadBalancerRuleClassName:com.alibaba.cloud.nacos.ribbon.NacosRule# 用户服务走同集群优先orderservice:ribbon:NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RandomRule# 订单服务走纯随机为什么配在消费者侧因为发起调用的是消费者选择实例这个动作发生在消费者进程里Ribbon 的ILoadBalancer跑在客户端。提供方只管把自己注册上去、把cluster-name报给 Nacos它不知道也不会关心谁要调它、按什么规则调。所以规则配置写在order-service不写在user-service——这一点后面要进坑列表。改完重启order-service。工程里还开着 Ribbon 饥饿加载两行配置配合着看ribbon:eager-load:enabled:true# 开启饥饿加载clients:# 指定饥饿加载的服务名称-userservice第一次调用慢、后面快这是懒加载的典型表现eager-load就是让 Ribbon 在启动时就把userservice的实例列表拉下来第一次调用不用等。工程结构改动涉及的模块在终态工程里是这样组织的cloud-demo ├── pom.xml # 聚合父工程统一管理 Spring Cloud Alibaba 版本 ├── eureka-server # 早期用 Eureka后切 Nacos ├── feign-api # 抽出 Feign 的 Client 与实体被 order/user 共同依赖 ├── gateway # 网关10010 ├── user-service │ ├── src/main/java/cn/itcast/user │ │ ├── UserApplication.java │ │ ├── web/UserController.java # GET /user/{id} │ │ ├── service/UserService.java │ │ ├── mapper/UserMapper.java │ │ └── pojo/User.java │ └── src/main/resources/application.yml # cluster-name 写在这里 └── order-service ├── src/main/java/cn/itcast/order │ ├── OrderApplication.java # EnableFeignClients RestTemplate Bean │ ├── web/OrderController.java # GET /order/{orderId} │ ├── service/OrderService.java # 通过 Feign 远程调 userservice │ ├── mapper/OrderMapper.java │ └── pojo/Order.java └── src/main/resources/application.yml # NacosRule 写在这里order-service发起远程调用的代码Feign 版终态工程实际在用的写法packagecn.itcast.order.service;importcn.itcast.feign.clients.UserClient;importcn.itcast.feign.pojo.User;importcn.itcast.order.mapper.OrderMapper;importcn.itcast.order.pojo.Order;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;ServicepublicclassOrderService{AutowiredprivateOrderMapperorderMapper;AutowiredprivateUserClientuserClient;publicOrderqueryOrderById(LongorderId){// 1.查询订单OrderorderorderMapper.findById(orderId);// 2.用Feign远程调用服务名 userservice 会走 Ribbon 的负载均衡UseruseruserClient.findById(order.getUserId());// 3.封装user到Orderorder.setUser(user);// 4.返回returnorder;}}UserClient上标FeignClient(userservice)Feign 底层复用 Ribbon所以userservice.ribbon.NFLoadBalancerRuleClassName这条配置对 Feign 调用同样生效——这不是巧合是同一套ILoadBalancer在干活。完整验证流程原理讲完了但这篇最有说服力的地方是能一步步复现出来。下面这套步骤照抄即可。造出一个跨集群的实例列表user-service的application.yml里把集群写上终态工程里这段是注释掉的验证时需要放开spring:cloud:nacos:server-addr:localhost:8848discovery:cluster-name:HZ# 集群名称然后启动三份实例。前两份直接用配置文件里的HZ第三份在 IDEA 的 VM options 里覆盖-Dserver.port8083-Dspring.cloud.nacos.discovery.cluster-nameSH命令行起也行java-Dserver.port8083\-Dspring.cloud.nacos.discovery.cluster-nameSH\-jaruser-service.jar启动完去 Nacos 控制台 → 服务列表 →userservice→ 详情应该看到HZ集群下两个实例、SH集群下一个实例。让消费者站在 HZorder-service的cluster-name配成HZ重启。同样在控制台确认orderservice的集群是HZ——这一步别跳过配错集群名后面的现象会完全不同。连打五次看落在谁身上foriin12345;docurl-shttp://localhost:8088/order/101;echo;done观察点在两个 HZ 实例的控制台日志。工程里开了cn.itcast: debug每个实例处理请求时会打出 MyBatis 的 SQLlogging:level:cn.itcast:debugpattern:dateformat:MM-dd HH:mm:ss:SSS预期结果8081和8082的控制台都有 SQL 输出8083SH 集群的控制台干干净净一条都没有五次调用在 8081/8082 之间的分布没有固定循环规律是随机如果8083也打了日志说明规则没生效——先回去确认userservice.ribbon.NFLoadBalancerRuleClassName的层级缩进对不对。想在响应里直接看到端口、省得来回切控制台可以临时加一个探针接口packagecn.itcast.user.web;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;RestControllerRequestMapping(/probe)publicclassProbeController{Value(${server.port})privateStringport;Value(${spring.cloud.nacos.discovery.cluster-name:UNKNOWN})privateStringclusterName;/** * 联调探针返回当前实例的端口与集群用来确认请求落到了哪个实例 * 路径/probe/instance */GetMapping(/instance)publicStringinstance(){returnportport, clusterclusterName;}}然后在order-service里换成一个能带出探针结果的调用看一眼就行。上线前记得删掉。停掉 HZ制造跨集群把8081、8082两个进程停掉让HZ集群彻底没人。回到 Nacos 控制台刷新userservice会显示健康实例数为 1点进详情HZ集群是空的。再打请求curl-shttp://localhost:8088/order/101;echo这次能正常返回功能不降级这是重点8083的控制台开始出现 SQL 日志同时order-service的控制台出现前面那条A cross-cluster call occurs告警。整个过程用一张时序图看更清楚userservice-3(SH:8083)userservice-1(HZ:8081)Nacos 注册中心orderservice(HZ)浏览器userservice-3(SH:8083)userservice-1(HZ:8081)Nacos 注册中心orderservice(HZ)浏览器阶段一HZ 集群健康阶段二HZ 实例全部下线GET /order/101拉取 userservice 实例列表8081(HZ), 8082(HZ), 8083(SH)NacosRule同集群过滤后随机HTTP 调用UserOrderGET /order/101拉取 userservice 实例列表仅 8083(SH)同集群无实例打印 WARN跨集群 HTTP 调用UserOrder成功但跨了机房权重的影响集群解决的是往哪个机房打权重解决的是同一批实例里各打多少。Nacos 控制台的实例列表里每个实例都有权重点编辑就能改默认值是 1。规则是这样的权重会影响选择结果。权重高的实例被挑中的概率更大用来让配置好的机器多扛点流量。权重为 0 的实例不会被选中。这是权重最实用的一条用法想让某台机器退出流量不用停进程把权重改 0 就行进程还在、注册还在、健康状态还在但没有任何请求会过去。第二条有个经典的误判把权重改成 0 之后Nacos 控制台里这个实例依然显示为健康实例健康实例数不变。有同学看到健康就以为流量还过去其实请求早就绕开它了。反过来排查问题也要注意——健康实例数是 3实际参与负载均衡的可能只有 2 个。权重为 0 的典型场景灰度发布新版本先挂上去、权重设 0确认启动没问题再放开、机器维护不动进程先摘流量、以及临时降级容量不足的节点。和 Ribbon 自带策略的关系NacosRule并没有另起炉灶。它实现的是和RandomRule、RoundRobinRule完全相同的com.netflix.loadbalancer.IRule接口所以配置方式一模一样userservice:ribbon:NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RandomRule# 纯随机# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule # 轮询# NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 同集群优先上一篇学怎么改 Ribbon 规则NFLoadBalancerRuleClassName这个键、按服务名做前缀、写在消费者侧这一篇只是把值从RandomRule换成NacosRule。换个值就换了一套实例选取算法——这就是面向接口换来扩展性的地方。版本提醒如果用的是Spring Cloud Alibaba 2021.0.1.0 及之后的版本Ribbon 已经被移除NFLoadBalancerRuleClassName这套配置不再生效NacosRule也不在com.alibaba.cloud.nacos.ribbon包下了。新版本走的是 Spring Cloud LoadBalancerspring:cloud:loadbalancer:nacos:enabled:true# 开启 Nacos 的负载均衡策略效果等同于 NacosRule本文基于课程中的 Ribbon 版本HoxtonSpring Cloud Alibaba 2.2.x一系来写因为原始工程的配置就是这么写的。老版本看到ribbon字样、新版本看到loadbalancer.nacos字样两者是同源的两代实现别混着抄。五个实战坑坑现象正确做法规则配到提供者侧消费者行为毫无变化还是跨机房规则属于调用方配在order-service的application.yml类名写错不报错、不启动失败行为看起来正常但一直在跨机房类名拼写必须精确到com.alibaba.cloud.nacos.ribbon.NacosRule排查时直接搜这条警告日志只给部分服务配了cluster-name没配的那个服务上报的集群名是默认值与消费者永远不同集群优先级失效参与集群划分的每个服务都要配包括消费者自己集群内实例全挂后无人知晓功能正常只是悄悄跨机房延迟无声上涨盯A cross-cluster call occurs告警接到告警去查 HZ 实例为什么下线权重改 0 后误判控制台仍显示健康以为流量还在走记牢健康 ≠ 参与负载均衡权重 0 不参与选择第三个坑的排查手法很简单拿到一条跨集群告警先去 Nacos 控制台对比消费者和提供者上报的集群名十有八九是一边没配或配错了值。配置速览配置项位置作用spring.cloud.nacos.discovery.cluster-name每个服务自己的application.yml声明本实例所属集群userservice.ribbon.NFLoadBalancerRuleClassName消费者order-service指定调userservice时用的负载均衡规则ribbon.eager-load.enabled/clients消费者启动时预加载实例列表避免首次调用变慢实例权重Nacos 控制台 → 服务详情 → 编辑影响被选中概率0 表示不参与负载均衡官方文档Nacos 官方文档 - 服务发现Spring Cloud Alibaba - Nacos DiscoverySpring Cloud LoadBalancer 文档总结集群属性只是把实例分了组分组本身不改变调用行为。真正决定往哪打的是负载均衡规则。默认的ZoneAvoidanceRule认的是 Eureka 的 zone跟 Nacos 的cluster-name是两套字段所以它做不到同集群优先只会轮询。NacosRule的顺序是同集群优先 → 集群内随机 → 都没有才跨集群并打 WARN。集群内是随机别把它当成轮询。那条跨集群告警是免费的监控点价值在于把跨机房这种性能问题提前暴露出来。集群内实例挂光不影响功能只影响延迟。配置两处都在消费者侧本服务配cluster-name按被调用方服务名配NacosRule。类名写错会静默回退默认策略出问题先搜告警日志。权重能调流量分配权重 0 的实例不参与负载均衡但仍然健康——这个看起来还健康是最容易被误判的地方。