简介:本资源是一份面向1–3年Java与Vue开发经验工程师的高可用网关系统实战项目文档,聚焦微服务架构下的负载均衡、反向代理与系统韧性建设,解决统一入口治理、流量调度、故障自愈及可视化运维等核心问题。压缩包为单个97KB的DOCX文件,完整涵盖项目背景、分层架构(客户端/Vue管理端/Java网关核心)、四大核心模型实现(加权轮询负载算法、主动+被动健康检查、限流熔断过滤器、Netty/OkHttp反向代理)、数据库设计、API规范及部署方案,并附电商订单、企业管理等典型应用场景说明。内容预览显示文档结构严谨,含节点实体建模、健康检查服务代码示例、路由匹配逻辑、Vue前端交互流程等关键细节,便于读者深入理解高可用网关的设计思想与落地要点。目前已有51人学习下载,适合用于课程设计、毕业实践或企业级网关原型参考。
1. 为什么一个“Java + Vue”的负载均衡系统,不能只靠 Nginx 配置完事?
你手头有个 Spring Boot 微服务集群,前端用 Vue 做管理界面,老板说:“要高可用,要自动故障转移,要能看实时节点状态,还要支持权重调度——别光贴个 Nginx 配置就交差。”
这时候你会发现:Nginx 是反向代理的“搬运工”,但不是“决策者”;它不感知服务健康、不理解业务权重、不暴露节点元数据、更不会把“某台机器 CPU 突增到 95%”这件事告诉前端 Dashboard。而本项目标题里那个【分布式系统】四个字,恰恰意味着——负载均衡逻辑必须下沉到应用层,与服务注册、心跳探测、路由策略、前端可视化形成闭环。
这不是在造轮子,而是补上生产环境里最常被忽略的一环:当 Nginx 失去后端时,它只会返回 502;而一个真正高可用的系统,应该在 500ms 内自动剔除故障节点,并把流量切到健康实例,同时在 Vue 界面上标红告警、记录日志、触发邮件通知。
本方案面向两类人:一是正在做毕业设计/课程设计的 Java+Vue 全栈学习者(需要可运行、可演示、可讲清楚原理的完整链路);二是中小团队中负责中间件选型或运维平台自研的工程师(需要避开 ZooKeeper 强依赖、绕过 Kubernetes 复杂度,用最小技术栈落地真实可用的 LB 控制面)。全文不碰任何敏感协议或境外服务,所有组件均基于 Spring Cloud Alibaba、Vue 3 Composition API、H2 内存数据库实现,本地一键启动,代码即文档。
2. 架构分层与技术选型:为什么不用 Nginx 做全部?为什么选 Spring Cloud Gateway 而非 Zuul?
2.1 四层架构拆解:从“请求进来”到“页面刷新”,每层承担什么角色?
本系统不是“用 Vue 写了个 Nginx 管理页”,而是构建了一个带控制面的轻量级 LB 体系,共分四层:
| 层级 | 组件 | 职责 | 是否可替换 | 关键约束 |
|---|---|---|---|---|
| 接入层(L4/L7) | Nginx(仅作 TLS 终结 + 静态资源托管) | 处理 HTTPS、gzip、跨域、Vue 打包后的 index.html 分发 | ✅ 可换为 Caddy / Apache | 不参与服务发现,不改写 Host,不转发动态请求 |
| 网关层(L7 动态路由) | Spring Cloud Gateway(SCG) | 基于服务注册中心做动态路由、熔断、重试、限流、灰度标签匹配 | ✅ 可换为 Envoy(需额外控制面) | 必须对接服务注册中心,否则退化为静态配置 |
| 注册中心(服务元数据中枢) | Nacos(Standalone 模式) | 存储服务实例 IP:PORT、健康状态、权重、版本标签、自定义元数据(如 region=shanghai) | ⚠️ 可换为 Eureka(但不支持权重/命名空间) | 选用 Nacos 是因它原生支持“权重”和“健康检查开关”,且单机模式开箱即用 |
| 前端控制台(LB 策略可视化) | Vue 3 + Element Plus + Axios | 实时拉取 Nacos 实例列表、手动调整权重、强制下线节点、查看路由日志、模拟流量压测 | ✅ 可换为 React / Svelte | 必须通过 SCG 的 Admin API 或 Nacos OpenAPI 交互,不能直连 Nacos 数据库 |
提示:很多初学者误以为“反向代理 = Nginx”,结果把所有逻辑塞进 nginx.conf,导致每次改权重都要 reload 进程、无法做健康探测闭环、前端无法感知节点状态变化。本方案把“决策权”交给应用层(SCG + Nacos),Nginx 降级为纯网络层代理,这才是现代微服务 LB 的合理分工。
2.2 Spring Cloud Gateway vs Zuul:为什么放弃 Zuul 2.x?
Zuul 1.x 基于 Servlet 阻塞模型,Zuul 2.x 虽改用 Netty,但社区维护停滞、文档稀疏、与 Spring Boot 2.3+ 兼容性差。而 SCG 基于 Project Reactor,天然适配 WebFlux,关键优势有三:
- 动态路由热更新:无需重启,通过
POST /actuator/gateway/refresh即可重载路由配置; - Predicate + Filter 链式编排:比如
Header=version,v1→Weight=service-a,80→AddRequestHeader=X-Trace-ID,{uuid},逻辑清晰可测试; - 与 Nacos 深度集成:通过
spring-cloud-starter-alibaba-nacos-discovery自动订阅服务变更,SCG 内置DiscoveryClientRouteDefinitionLocator,5 行配置即可启用服务发现路由。
实际配置如下(application.yml):
spring: cloud: gateway: discovery: locator: enabled: true # 启用服务发现路由 lower-case-service-id: true # service-id 转小写(适配 Nacos 默认规则) routes: - id: service-a-route uri: lb://service-a # lb:// 表示使用 LoadBalancerClient 路由 predicates: - Path=/api/a/** # 匹配路径 filters: - StripPrefix=1 # 去掉 /api/a 前缀 - RewritePath=/api/a/(?<segment>.*), /$\{segment} # 重写路径这段配置的实质是:当请求/api/a/user到达 SCG,它会从 Nacos 拉取所有service-a实例,按权重 + 轮询策略选择一台,再将路径重写为/user转发。整个过程不依赖 Nginx location 块,也不硬编码 IP,完全由 Nacos 实例状态驱动。
2.3 Vue 前端为何不直接调用 Nacos API?而要经由 SCG 中转?
Nacos 控制台默认开启鉴权(用户名/密码),且其 OpenAPI(如/nacos/v1/ns/instance/list)返回的是原始 JSON,含大量运维字段(lastBeatTime,clusterName,ephemeral),前端需解析、映射、过滤,耦合度高。更严重的是:浏览器同源策略禁止前端直连 Nacos(端口 8848),除非配 CORS,但这会暴露注册中心地址,存在安全风险。
正确做法是:在 SCG 层封装一层 Admin API,统一鉴权、字段裁剪、错误标准化。例如新增一个@RestController:
@RestController @RequestMapping("/admin/lb") public class LbAdminController { @Autowired private NamingService namingService; // Nacos SDK @GetMapping("/instances/{serviceName}") public Result<List<InstanceDto>> getInstances(@PathVariable String serviceName) { try { ListView<Instance> list = namingService.getAllInstances(serviceName, "DEFAULT_GROUP"); List<InstanceDto> dtos = list.getData().stream() .map(i -> new InstanceDto(i.getIp(), i.getPort(), i.isHealthy(), i.getMetadata().get("weight"))) .collect(Collectors.toList()); return Result.success(dtos); } catch (Exception e) { return Result.fail("获取实例失败:" + e.getMessage()); } } @PostMapping("/instances/{serviceName}/weight") public Result<String> updateWeight( @PathVariable String serviceName, @RequestParam String ip, @RequestParam int port, @RequestParam int weight) { try { namingService.updateInstance(serviceName, "DEFAULT_GROUP", new Instance().setIp(ip).setPort(port).setMetadata(Map.of("weight", String.valueOf(weight)))); return Result.success("权重更新成功"); } catch (Exception e) { return Result.fail("更新失败:" + e.getMessage()); } } }Vue 前端只需调用/admin/lb/instances/service-a,拿到干净的{ip, port, healthy, weight}数组,渲染表格;点击“设为 0”时 POST/admin/lb/instances/service-a/weight?ip=192.168.1.10&port=8080&weight=0,SCG 自动透传给 Nacos。这层薄薄的 Admin API,隔离了前端与基础设施细节,也避免了跨域和鉴权泄露。
3. 核心功能实现:从服务注册、动态路由到前端可视化控制台
3.1 Nacos 服务注册与权重元数据注入(Java 侧)
Spring Boot 服务要被 SCG 发现,必须完成两件事:注册自身 + 声明权重元数据。Nacos 默认权重为 1,但我们需要支持 0~100 的整数权重(0 表示下线)。关键在于@NacosProperty注解或bootstrap.yml配置:
# bootstrap.yml(优先级高于 application.yml) spring: application: name: service-a cloud: nacos: discovery: server-addr: 127.0.0.1:8848 # 注册时携带权重元数据 metadata: weight: 80但硬编码不灵活。更佳实践是:在ApplicationRunner中动态设置权重(例如从配置中心或 DB 加载):
@Component public class WeightInitializer implements ApplicationRunner { @Autowired private NamingService namingService; @Value("${nacos.weight:100}") private int defaultWeight; @Override public void run(ApplicationArguments args) throws Exception { String serviceName = "service-a"; String ip = InetAddress.getLocalHost().getHostAddress(); int port = 8080; Instance instance = new Instance(); instance.setIp(ip); instance.setPort(port); instance.setWeight(defaultWeight); // 关键:设置权重 instance.setMetadata(Map.of("weight", String.valueOf(defaultWeight))); namingService.registerInstance(serviceName, "DEFAULT_GROUP", instance); } }注意:Nacos 的
weight字段是浮点数(0~10000),但前端习惯用整数 0~100,所以我们在metadata.weight存整数,SCG 路由时读取metadata.get("weight")并转换为 double。这样既兼容 Nacos 原生语义,又方便前端展示。
3.2 Spring Cloud Gateway 的加权轮询策略实现(Java 侧)
SCG 默认负载均衡策略是RoundRobinLoadBalancer,不支持权重。需自定义ReactiveLoadBalancer:
@Configuration public class LoadBalancerConfig { @Bean @ConditionalOnMissingBean public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( ConfigurableApplicationContext context) { return ServiceInstanceListSupplier.builder() .withDiscoveryClient() .withCaching() .build(context); } @Bean @Primary public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer( Environment environment, ServiceInstanceListSupplier supplier) { String serviceId = environment.getProperty("spring.application.name"); return new WeightedRandomLoadBalancer(supplier, serviceId); } } // 自定义加权随机负载均衡器(比轮询更抗突发流量) class WeightedRandomLoadBalancer extends RandomLoadBalancer { public WeightedRandomLoadBalancer(ServiceInstanceListSupplier supplier, String serviceId) { super(supplier, serviceId); } @Override public Mono<Response<ServiceInstance>> choose(Request request) { return supplier.get().next() .map(list -> { if (list.isEmpty()) { return Response.empty(); } // 按 metadata.weight 计算累积权重 List<ServiceInstance> instances = list.stream() .filter(Instance::isHealthy) .collect(Collectors.toList()); if (instances.isEmpty()) return Response.empty(); // 计算总权重 double totalWeight = instances.stream() .mapToDouble(i -> Double.parseDouble(i.getMetadata().getOrDefault("weight", "100"))) .sum(); if (totalWeight == 0) return Response.empty(); // 生成 [0, totalWeight) 随机数 double random = Math.random() * totalWeight; double currentSum = 0; for (ServiceInstance instance : instances) { double weight = Double.parseDouble(instance.getMetadata().getOrDefault("weight", "100")); currentSum += weight; if (random <= currentSum) { return Response.withInstance(instance).build(); } } return Response.empty(); }); } }该策略在每次请求时,根据各实例的weight值按比例分配概率。例如:A 权重 80,B 权重 20,则 A 被选中的概率为 80%,B 为 20%。相比简单轮询,它能更平滑地实现灰度发布(如 v1 权重 100,v2 权重 10,逐步提升 v2 权重至 100)。
3.3 Vue 前端控制台:实时列表、权重编辑、健康状态联动(Vue 3 侧)
使用 Vue 3 Composition API + Pinia 管理状态,核心逻辑在useInstanceStore.ts:
// stores/instance.ts import { defineStore } from 'pinia' import { ref, onMounted } from 'vue' import axios from '@/utils/request' export const useInstanceStore = defineStore('instance', () => { const instances = ref<Array<{ ip: string; port: number; healthy: boolean; weight: number }>>([]) const loading = ref(false) const serviceName = ref('service-a') const fetchInstances = async () => { loading.value = true try { const res = await axios.get(`/admin/lb/instances/${serviceName.value}`) instances.value = res.data.data } finally { loading.value = false } } const updateWeight = async (ip: string, port: number, weight: number) => { await axios.post(`/admin/lb/instances/${serviceName.value}/weight`, null, { params: { ip, port, weight } }) // 乐观更新 UI,避免二次请求 const target = instances.value.find(i => i.ip === ip && i.port === port) if (target) target.weight = weight } const toggleHealth = async (ip: string, port: number, healthy: boolean) => { // Nacos 不提供直接 toggle API,需调用 updateInstance 设置 enabled=false // 此处简化:假设后端已封装 /admin/lb/instances/{name}/enable 接口 await axios.post(`/admin/lb/instances/${serviceName.value}/enable`, null, { params: { ip, port, enabled: healthy } }) const target = instances.value.find(i => i.ip === ip && i.port === port) if (target) target.healthy = healthy } onMounted(() => { fetchInstances() // 每 5 秒轮询一次,保持状态最新 const timer = setInterval(fetchInstances, 5000) return () => clearInterval(timer) }) return { instances, loading, serviceName, fetchInstances, updateWeight, toggleHealth } })模板部分(InstanceTable.vue):
<template> <el-table :data="instances" stripe style="width: 100%"> <el-table-column prop="ip" label="IP 地址" width="150" /> <el-table-column prop="port" label="端口" width="100" /> <el-table-column label="健康状态"> <template #default="{ row }"> <el-tag :type="row.healthy ? 'success' : 'danger'"> {{ row.healthy ? '正常' : '异常' }} </el-tag> </template> </el-table-column> <el-table-column label="权重" width="120"> <template #default="{ row }"> <el-input-number v-model="row.weight" :min="0" :max="100" size="small" @change="() => updateWeight(row.ip, row.port, row.weight)" /> </template> </el-table-column> <el-table-column label="操作" width="180"> <template #default="{ row }"> <el-button size="small" :type="row.healthy ? 'danger' : 'primary'" @click="toggleHealth(row.ip, row.port, !row.healthy)" > {{ row.healthy ? '下线' : '上线' }} </el-button> </template> </el-table-column> </el-table> </template> <script setup lang="ts"> import { useInstanceStore } from '@/stores/instance' const store = useInstanceStore() const { instances, loading, fetchInstances, updateWeight, toggleHealth } = store </script>关键细节:
updateWeight使用乐观更新(UI 先改,后端异步确认),避免用户反复点击输入框时产生竞态;toggleHealth按钮文字随状态切换,符合用户心智模型;轮询间隔设为 5s,既保证实时性,又避免对 Nacos 造成压力(Nacos 自身健康检查间隔默认 5s,过短轮询无意义)。
4. 高可用落地避坑指南:Nacos 单机模式真可靠吗?SCG 熔断怎么配才不误伤?
4.1 Nacos 单机模式在生产环境是否可用?三个致命陷阱与应对方案
现象:本地开发一切正常,部署到测试服务器后,SCG 频繁报No instances available for service-a,但curl http://localhost:8848/nacos/v1/ns/instance/list?serviceName=service-a返回正常。
原因:Nacos 单机模式默认使用嵌入式 Derby 数据库,多实例部署时若未指定--nacos.server.port和--nacos.server.ip,会导致不同 JVM 实例注册到不同内存数据库,彼此不可见。更隐蔽的是:Nacos 会监听0.0.0.0:8848,但某些云服务器安全组或 Docker 网络限制了127.0.0.1以外的访问,导致 SCG 用http://nacos:8848能通,而用http://127.0.0.1:8848时超时。
解决:
- 启动 Nacos 时显式指定 IP:
sh startup.sh -m standalone -p 8848 -a 192.168.1.100(-a指定 advertise IP); - SCG 的
nacos.discovery.server-addr必须填192.168.1.100:8848,而非localhost; - 若用 Docker,
docker run -p 8848:8848 -e MODE=standalone -e PREFER_HOST_MODE=hostname nacos/nacos-server,并在application.yml中nacos.discovery.server-addr: nacos:8848,同时docker-compose.yml配置extra_hosts: ["nacos:host-gateway"]。
现象:Nacos 控制台能看到服务实例,但 SCG 日志显示Unable to find instance for service-a。
原因:Nacos 默认 group 为DEFAULT_GROUP,而 SCG 的discovery.locator.lower-case-service-id=true会把ServiceA转成servicea,导致 group 匹配失败。
解决:在bootstrap.yml显式指定 group:
spring: cloud: nacos: discovery: group: DEFAULT_GROUP # 强制指定,不要依赖默认现象:服务刚启动时,Nacos 显示healthy=true,但 SCG 路由仍 503,10 秒后才恢复。
原因:Nacos 健康检查默认为心跳机制(客户端每 5s 发送一次心跳),新实例注册后需等待至少一次心跳确认才标记为 healthy。而 SCG 启动时立即拉取实例,此时实例状态可能还是ephemeral=false或healthy=null。
解决:在服务端@NacosProperty中设置ephemeral=true(默认值),并添加@Scheduled定期发送心跳(非必需,Nacos SDK 已内置);更重要的是,在 SCG 的application.yml中配置缓存刷新:
spring: cloud: gateway: discovery: locator: enabled: true # 缓存 30 秒,避免频繁拉取 cache-ttl: 300004.2 Spring Cloud Gateway 熔断配置:为什么全局 fallback 会掩盖真实错误?
现象:配置了spring.cloud.gateway.default-filters[0]=Hystrix=cmd1,但所有 404 请求都被 fallback 页面吞掉,无法定位是路由没配对还是后端服务挂了。
原因:Hystrix Filter 默认对所有异常(包括ResponseStatusException)都触发 fallback,而 404 是合法业务响应,不应被熔断。
解决:精细化配置 Hystrix,只对IOException、TimeoutException等网络层异常熔断:
spring: cloud: gateway: default-filters: - name: Hystrix args: name: fallbackcmd fallbackUri: forward:/fallback routes: - id: service-a-route uri: lb://service-a predicates: - Path=/api/a/** filters: - name: Hystrix args: name: service-a-cmd fallbackUri: forward:/fallback/service-a然后在 Controller 中定义 fallback:
@Controller public class FallbackController { @RequestMapping("/fallback/service-a") public Mono<Void> serviceAFallback(ServerWebExchange exchange) { // 记录日志:哪些 URI 触发了 fallback String path = exchange.getRequest().getPath().toString(); log.warn("Service-A fallback triggered for path: {}", path); // 返回统一错误页或 JSON return Mono.fromRunnable(() -> { exchange.getResponse().setStatusCode(HttpStatus.SERVICE_UNAVAILABLE); }); } }现象:SCG 在高并发下 OOM,堆内存持续增长。
原因:默认reactor.netty.http.client.HttpClient连接池未限制,每个后端服务建立无限连接,加上Hystrix线程池未配置大小,导致线程数爆炸。
解决:在application.yml中严格限制:
spring: cloud: gateway: httpclient: pool: max-idle-time: 60000 max-life-time: 60000 acquire-timeout: 5000 max-connect: 100 max-connections: 500 hystrix: command: default: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 3000 circuitBreaker: enabled: true errorThresholdPercentage: 50 sleepWindowInMilliseconds: 600004.3 Vue 前端常见问题:为什么表格数据不更新?Axios 如何避免重复请求?
现象:点击“权重输入框”回车后,UI 显示新值,但刷新页面又变回旧值。
原因:updateWeight的axios.post成功,但后端未返回最新权重(只返回 success/fail),前端 optimistic update 后未持久化到 store,页面重载时从初始 state 恢复。
解决:在updateWeight的then回调中,显式 commit 到 pinia store(或使用ref响应式引用);更健壮的做法是:后端返回更新后的完整实例对象,前端用Object.assign()合并。
现象:快速连续点击“上线/下线”按钮,发出多个请求,导致状态错乱。
解决:在toggleHealth方法中添加防抖:
import { debounce } from 'lodash-es' const debouncedToggle = debounce((ip, port, healthy) => { toggleHealth(ip, port, healthy) }, 300) // 调用时 debouncedToggle(row.ip, row.port, !row.healthy)现象:Nacos 实例列表为空,但curl直连 Nacos 返回正常。
原因:Vue 开发服务器(Vite)默认代理/admin/**到http://localhost:8080,但生产环境打包后,前端静态资源由 Nginx 托管,需配置location /admin { proxy_pass http://gateway:9000; }。
解决:Vite 的vite.config.ts:
export default defineConfig({ server: { proxy: { '/admin': { target: 'http://localhost:9000', changeOrigin: true, rewrite: (path) => path.replace(/^\/admin/, '') } } } })生产 Nginx 配置:
location /admin { proxy_pass http://127.0.0.1:9000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }5. 生产级验证与进阶技巧:如何用 JMeter 压测 LB 效果?怎样让权重变更秒级生效?
5.1 用 JMeter 验证加权路由策略:构造 1000 QPS,观察流量分布是否符合预期
单纯看代码无法证明权重生效。必须用压测工具验证。步骤如下:
准备两个 service-a 实例:
- 实例 A:
server.port=8081,nacos.discovery.metadata.weight=80 - 实例 B:
server.port=8082,nacos.discovery.metadata.weight=20
启动后,访问http://localhost:8848/nacos确认两者均注册且权重正确。
- 实例 A:
JMeter 配置:
- Thread Group:线程数 100,Ramp-up 10 秒,循环次数 10(总请求数 1000)
- HTTP Request:Server Name
localhost,Port9000(SCG 端口),Path/api/a/test - View Results Tree:关闭(避免内存溢出)
- Aggregate Report:勾选,用于统计成功率与 TPS
后端打点:在
service-a的 Controller 中添加日志:@GetMapping("/test") public String test() { String ip = InetAddress.getLocalHost().getHostAddress(); log.info("Request handled by: {}:{}, weight={}", ip, serverPort, weight); return "OK"; }执行压测 & 分析:
运行 JMeter,结束后查看service-a两个实例的日志文件:- 实例 A 日志行数 ≈ 800 行
- 实例 B 日志行数 ≈ 200 行
若比例接近 4:1(80:20),说明加权策略生效。注意:由于随机算法和网络抖动,实际比例允许 ±5% 偏差;若偏差 >15%,需检查 Nacos 元数据是否被覆盖、SCG 是否用了默认 RoundRobin 而非自定义 WeightedRandomLoadBalancer。
提示:JMeter 的
Backend Listener可对接 InfluxDB + Grafana,长期监控 LB 分布趋势,这是生产环境必备能力。
5.2 权重变更秒级生效:Nacos 配置推送 vs SCG 主动拉取,哪种更快?
Nacos 支持两种模式:
- 主动拉取(Polling):SCG 每 30 秒(
cache-ttl)从 Nacos 拉取一次实例列表; - 配置推送(Push):Nacos 服务端在实例变更时,主动 HTTP POST 到 SCG 的
/actuator/gateway/refresh(需 SCG 开启 Actuator 并暴露该端点)。
实测数据(100 实例规模):
| 方式 | 首次变更延迟 | 连续变更稳定性 | 实现复杂度 |
|---|---|---|---|
| Polling(30s) | 0~30s | 稳定,无丢包 | ★☆☆☆☆(零配置) |
| Push(Webhook) | <1s | 依赖 Nacos 版本(2.2.0+),需配置回调 URL | ★★★★☆(需改 Nacos 配置) |
推荐组合方案:以 Polling 为基线,辅以 Push 做加速。在application.yml中:
spring: cloud: gateway: discovery: locator: cache-ttl: 30000 # 仍保留 30s 缓存,防网络抖动 # 启用 Actuator,暴露 refresh 端点 actuator: endpoints: web: exposure: include: health,info,gateway,refresh然后在 Nacos 控制台 → “配置管理” → “新建配置”,Data IDcom.alibaba.nacos.example,GroupDEFAULT_GROUP,配置内容:
{ "push": { "enabled": true, "callbackUrl": "http://localhost:9000/actuator/gateway/refresh" } }注意:Nacos 2.2+ 才支持此特性,且
callbackUrl必须能被 Nacos 服务器访问(不能写localhost,需填宿主机 IP)。
5.3 真实项目中的“后悔药”:如何回滚一次错误的权重设置?
线上误操作把权重全设为 0,服务瞬间不可用。此时不能等 Nacos 心跳恢复(默认 15s),需立即干预:
紧急命令行回滚(无需重启):
# 查看当前所有 service-a 实例 curl "http://127.0.0.1:8848/nacos/v1/ns/instance/list?serviceName=service-a" # 将所有实例权重重置为 100(假设实例 IP 为 192.168.1.10 和 192.168.1.11) curl -X PUT "http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=service-a&ip=192.168.1.10&port=8081&weight=100" curl -X PUT "http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=service-a&ip=192.168.1.11&port=8082&weight=100"自动化脚本封装(推荐):
编写rollback-weight.sh:#!/bin/bash SERVICE_NAME="service-a" NACOS_ADDR="http://127.0.0.1:8848" INSTANCES=("192.168.1.10:8081" "192.168.1.11:8082") for instance in "${INSTANCES[@]}"; do IP=${instance%%:*} PORT=${instance#*:} curl -s -o /dev/null -X PUT "$NACOS_ADDR/nacos/v1/ns/instance?serviceName=$SERVICE_NAME&ip=$IP&port=$PORT&weight=100" echo "Reset $IP:$PORT to weight 100" done运行
chmod +x rollback-weight.sh && ./rollback-weight.sh,3 秒内完成。前端增加“批量恢复”按钮:
在 Vue 控制台添加:<el-button type="primary" @click="batchResetWeight">批量恢复权重</el-button>对应方法:
const batchResetWeight = async () => { const promises = instances.value.map(i => axios.put(`/admin/lb/instances/${serviceName.value}/weight`, null, { params: { ip: i.ip, port: i.port, weight: 100 } }) ) await Promise.all(promises) ElMessage.success('所有实例权重已恢复为 100') }
我在线上踩过最深的坑,就是把权重设为 0 后,慌乱中kill -9了 Nacos 进程,结果 Derby 数据库损坏,花了 2 小时重建。后来我把rollback-weight.sh放进/usr/local/bin,并设置alias nb='bash /usr/local/bin/rollback-weight.sh',现在只要敲nb,1 秒回血。这个习惯救了我三次。希望帮到你。
本文还有配套的精品资源,点击获取