基于Java与Vue的高可用负载均衡与反向代理系统实战 简介面向具备Java与Vue基础、1-3年经验研发人员的高可用负载均衡与反向代理系统设计实例聚焦通过反向代理统一入口结合负载均衡、健康检查、限流熔断、故障转移等机制实现服务解耦与流量治理提升系统连续性与可运维性。资源为单个docx文档共1个文件压缩包整体约97KB内含项目背景、系统架构模型、数据库设计、API接口规范、部署流程、代码示例与数据库脚本内容紧凑、结构清晰适合作为课程设计、毕业设计或企业网关模块的参考实现。已有51人浏览学习。读者可从加权轮询节点选择器、健康检查服务、反向代理服务、限流过滤器等核心模块切入深入理解节点实体建模、路由匹配到节点选择、健康检查与故障转移的完整流程并借助Vue管理端设计掌握可视化运维与前后端交互逻辑。通过动手搭建和调试可重点关注网关核心链路的关键实现细节快速形成可演示、可扩展的高可用网关原型也可为微服务网关、中间件开发或系统架构设计提供系统化借鉴。1. 这套系统到底解决什么问题先给结论基于 Java 与 Vue 的高可用负载均衡与反向代理系统本质上是把 Nginx 那类基础设施的能力用 Java 重写一遍内核、用 Vue 重写一遍控制台。如果你所在团队的技术栈是 Spring Boot Vue又不希望引入 OpenResty、Lua 或 Go 网关那么这套方案能让你用熟悉的语言实现请求分发、健康检查和故障转移。它适合三类人需要给内部微服务做统一入口的后端工程师想把网关控制权交给运维同学的平台团队以及正在准备分布式系统相关面试、需要讲清楚负载均衡原理的开发者。系统的核心并不复杂就是一张路由表加上一套权重算法难点在动态更新和故障感知。读完你会得到一个可运行的骨架而不是一份概念 PPT。2. 负载均衡与反向代理的核心机制先理清两条链路2.1 反向代理为什么要用自己的 Java 实现反向代理对客户端隐藏后端服务地址客户端只请求代理节点由代理节点决定转发到哪台真实服务器。Java 实现反向代理最常见的做法是基于 Netty 或 Spring MVC 接收 HTTP 请求解析目标 Host 和 URI查路由表找到对应的上游服务器列表然后通过 HttpClient 转发请求并回写响应。// 核心转发逻辑接收客户端请求查路由表转发到上游 public void proxy(HttpServletRequest req, HttpServletResponse resp) { String host req.getServerName(); RouteRule rule routeTable.match(host, req.getRequestURI()); if (rule null) { resp.setStatus(404); return; } UpstreamServer target loadBalancer.select(rule.getUpstreamGroup()); // 转发时会丢失原始 Host 和 X-Forwarded-For需要手动补上 HttpURLConnection conn (HttpURLConnection) target.getUrl().openConnection(); conn.setRequestMethod(req.getMethod()); conn.setRequestProperty(X-Forwarded-For, req.getRemoteAddr()); conn.setRequestProperty(X-Forwarded-Host, host); // 读取上游响应并写回客户端 }这段代码有一个关键点X-Forwarded-For必须由代理节点追加而不是等待上游自己处理。很多初学者直接把HttpURLConnection的响应流写回客户端忽略了请求头透传导致后端拿到错误的客户端 IP。实际项目中你应该用RestTemplate或 OkHttp 替代HttpURLConnection前两者支持连接池复用而连接池是压测时吞吐量的分水岭。2.2 负载均衡算法的选型静态权重与动态反馈负载均衡算法的选择直接影响系统在高并发下的表现。常用算法有轮询、随机、哈希、最少连接数、加权轮询。静态算法适合后端性能均匀的场景动态算法更适合异构集群。这里给你一个可落地的加权轮询实现// 加权轮询每个上游节点有 weight 和 currentWeight // 每次选择 currentWeight 最大的节点然后减去总权重 public class WeightedRoundRobin { private final ListServer servers; private int totalWeight; public synchronized Server select() { Server selected null; for (Server server : servers) { server.currentWeight server.weight; totalWeight server.weight; if (selected null || server.currentWeight selected.currentWeight) { selected server; } } if (selected ! null) { selected.currentWeight - totalWeight; } return selected; } }这个算法叫做平滑加权轮询它的价值在于让请求分布保持均匀的同时不会出现短时间内的突发流量集中在同一台机器。如果后端节点配置差异很大比如一台 8 核 16G、另一台 4 核 8G你可以把权重设为 4:2。如果后端服务依赖数据库连接池或第三方 API上线的每个实例处理能力有波动我一般会在加权轮询的基础上叠加最少连接数的判断当前活跃请求数低于阈值时才使用权重分配这样能同时照顾到长连接和短连接场景。2.3 路由规则的数据结构设计路由表是这个系统的灵魂。常见的路由表设计是两级匹配第一级按域名匹配第二级按 URI 前缀匹配。放入内存中的数据结构是 ConcurrentHashMapkey 是域名 路径前缀value 是上游服务器组列表。动态更新时使用 CopyOnWriteArrayList 保存上游实例列表避免遍历时的并发修改异常。路由字段示例值说明domainapi.example.com精确匹配域名pathPrefix/order/**前缀匹配支持通配符upstreamGrouporder-service上游服务组名称timeout3000转发超时时间单位 msretryCount2失败重试次数路由表的更新频率通常很低但一旦更新必须实时生效。常见的做法是通过 ZooKeeper 或 Nacos 做配置中心本地维护一个版本号每次收到变更通知时增量更新而不是全量替换。全量替换的问题是如果某台代理节点更新失败新旧路由不一致会导致请求被转发到错误的服务。3. Java 后端实现从网关内核到管理接口3.1 项目结构与核心模块划分一个完整的 Java 反向代理项目我习惯按下面的模块拆分gateway-coreNetty 的请求接收、转发、响应回写gateway-router路由规则的加载、匹配、缓存gateway-balancer负载均衡算法包含平滑加权轮询、最少连接数gateway-health健康检查模块定时探测上游状态gateway-adminSpring Boot Web 模块提供 REST API 给 Vue 前端调用# 用 Maven 骨架创建工程 mvn archetype:generate -DgroupIdcom.demo -DartifactIdgateway # 目录结构 gateway/ gateway-core gateway-router gateway-balancer gateway-health gateway-admin3.2 Netty 网关内核的请求接入Spring Boot 自带的 Tomcat 可以处理请求转发但性能上限大约在每秒 1-2 万请求。要达到更高的吞吐量通常换用 Netty 作为接入层。Netty 的 EventLoop 模型避免了传统阻塞 IO 的线程上下文切换开销。下面是一个简化的 Netty 接入代码// Netty 服务端接收客户端请求后丢给业务线程池处理 public class GatewayServer { public void start(int port) throws Exception { EventLoopGroup boss new NioEventLoopGroup(1); EventLoopGroup worker new NioEventLoopGroup(Runtime.getRuntime().availableProcessors()); ServerBootstrap bootstrap new ServerBootstrap(); bootstrap.group(boss, worker) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializerSocketChannel() { Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new HttpServerCodec()); ch.pipeline().addLast(new HttpObjectAggregator(10 * 1024 * 1024)); ch.pipeline().addLast(new GatewayHandler()); } }); ChannelFuture future bootstrap.bind(port).sync(); future.channel().closeFuture().sync(); } }HttpObjectAggregator的参数是最大请求体大小如果你们会上传大文件需要调大这个值。Netty 接入层只负责 IO 读写具体的路由匹配和转发逻辑要放到独立的业务线程池里执行否则 Netty 的 EventLoop 被阻塞会导致整个网关雪崩。这里的线程池大小通常设置为 CPU 核心数的两倍队列容量控制在 1000 以内超出直接返回 503。3.3 健康检查与故障转移从静态列表到动态摘除任何高可用系统都无法回避故障转移。如果不做健康检查某台上游宕机后路由表依然保留它客户端就会间歇性报错。健康检查有两种模式主动探测和被动探测。主动探测是网关定时向后端发送 TCP 或 HTTP 请求判断端口是否可达被动探测是网关在转发请求时记录失败次数。// 主动健康检查每 5 秒探测一次连续失败 3 次摘除节点 Component public class HealthChecker implements Runnable { private final MapString, UpstreamServer servers; Override public void run() { for (UpstreamServer server : servers.values()) { boolean alive ping(server.getHost(), server.getPort()); if (!alive) { server.setHealthy(false); } else { server.setHealthy(true); } } } private boolean ping(String host, int port) { try (Socket socket new Socket()) { socket.connect(new InetSocketAddress(host, port), 1000); return true; } catch (IOException e) { return false; } } }被动探测的价值在于能发现 TCP 探测覆盖不到的异常比如 CPU 满载导致请求超时。我一般会同时开启两种模式主动探测负责快速感知宕机被动探测负责感知性能劣化。摘除节点后要有一个“半开”状态即节点被摘除后仍以较低频率试探恢复成功后重新加入路由表。如果摘除后立刻加入可能会引发抖动。3.4 管理接口给 Vue 前端提供配置的增删改查Vue 控制台需要获取路由规则、上游状态、实时流量这些数据。使用 Spring Boot 提供 REST API注意这几个接口是必备的RestController RequestMapping(/api) public class RouteController { // 返回所有路由规则 GetMapping(/routes) public ListRouteRule listRoutes() { ... } // 动态添加路由规则 PostMapping(/routes) public void addRoute(RequestBody RouteRule rule) { ... } // 修改某条规则的权重 PutMapping(/routes/{id}/weight) public void updateWeight(PathVariable Long id, RequestParam int weight) { ... } // 强制摘除上游节点 PostMapping(/servers/{id}/offline) public void offlineServer(PathVariable Long id) { ... } }这些接口要加权限控制至少要有 JWT 或 Session 登录校验否则任何人都能修改路由规则造成整个系统的可用性问题。实际项目中这个管理端通常部署在公司内网或者直接绑定 127.0.0.1 只允许本机访问。4. Vue 控制台设计实时状态面板与配置编辑器4.1 为什么用 Vue 而非其他前端框架Vue 的最大优势是响应式数据绑定非常适合网关控制台这种需要高频刷新状态页面的场景。ECharts 在 Vue 里做监控曲线、趋势图时配合度很高。另一个原因是 Vue 的生态组件丰富Element Plus 的表单校验、弹窗、表格等功能开箱即用团队上手成本低几天就能搭出完整控制台。4.2 核心页面路由规则管理路由规则管理是控制台最重要的模块。表格展示所有路由规则操作按钮包含编辑、删除、权重调整。新增或编辑规则时弹出表单让用户填写域名、路径前缀、上游列表、超时时间等字段。保存后通过 axios 调用后端 API 提交。template el-table :dataroutes stripe el-table-column propdomain label域名 width180 / el-table-column proppathPrefix label路径前缀 width180 / el-table-column propupstreamGroup label上游组 width160 / el-table-column label操作 width220 template #defaultscope el-button typeprimary sizesmall clickeditRoute(scope.row)编辑/el-button el-button typedanger sizesmall clickdeleteRoute(scope.row.id)删除/el-button /template /el-table-column /el-table /template表格数据是从/api/routes拉取的编辑时打开对话框修改完成后调PUT /api/routes/{id}提交。需要注意 Vue 的响应式特性直接修改数组某一项的内容页面不会自动更新一定要用this.$set或重新赋值整个数组。4.3 实时监控面板的轮询实现监控面板需要展示每台上游的 QPS、响应时间、健康状态。最简单的做法是使用setInterval每 3 秒拉取一次后端接口。但要注意页面切换到后台标签页时浏览器会降低定时器频率你可以利用visibilitychange事件暂停拉取减少无意义的请求。// 轮询拉取监控数据 export function usePolling(fetchFn, interval 3000) { onMounted(() { timer setInterval(fetchFn, interval); }); onBeforeUnmount(() { clearInterval(timer); }); }页面上用 ECharts 展示两条曲线一是总 QPS二是各节点响应时间。ECharts 的setOption方法支持增量更新只需要传入新的数据点即可。如果使用全量替换曲线会出现闪烁。响应时间的曲线可以加入红色阈值线当 P99 超过 1000ms 时触发视觉预警。4.4 高可用部署时 Vue 页面的 Nginx 配置Vue 前端和后端 API 分开部署时需要通过 Nginx 把/api开头的请求转发到 Java 服务。这个配置很简单但很容易出错server { listen 8080; # Vue 静态文件 root /usr/share/nginx/html; location /api/ { proxy_pass http://192.168.1.10:8888; } location / { try_files $uri $uri/ /index.html; } }proxy_pass http://192.168.1.10:8888;结尾没有带斜杠的写法是把/api/xxx原样转发给后端如果写成http://192.168.1.10:8888/则会把/api/前缀剥掉。分布式系统部署时最典型的问题就是这个地方写错导致后端接口 404。5. 高可用部署与压测验证把你的网关推向生产环境5.1 双节点部署加 Keepalived 浮动 IP单台网关节点本身就是单点故障点必须至少部署两台网关节点配合 Keepalived 实现 VIP 漂移。两台节点同时运行 Nginx 或 Java 网关进程Keepalived 对外提供一个虚拟 IP。当主节点宕机时备用节点接管客户端无感知。这个过程中后端服务地址完全不变仅需将域名解析指向 VIP 即可。Keepalived 的配置要注意组播冲突问题。默认的 multicast 方式在同一网段多个 Keepalived 集群时可能互相干扰建议改用单播方式在virtual_ipaddress段指定unicast_peer指向对端节点 IP。5.2 会话保持与 Cookie 粘性负载均衡系统的另一个关键点是会话保持。如果后端应用把 Session 存在本地内存那么用户的第二次请求被转发到另一台服务器就会丢失登录态。常见解决方案有三种Session 同步不推荐延迟高、Redis 统一存储推荐、客户端 Cookie 携带 Session ID 配合一致性哈希路由。在 Java 网关中实现 Cookie 粘性比较简单读取请求中的 JSESSIONID对它的哈希值取模固定路由到某台上游。但是这个方案的问题是一旦某台上游宕机该用户的请求就没有可用节点。我会在取模结果不可用时回退到最少连接数算法保证高可用优先于会话保持。5.3 压测指标与参数调优方向用压测工具验证网关性能时需要重点关注以下指标指标健康值问题排查方向QPS单节点 ≥ 1万Netty worker 线程数、业务线程池大小P99 延迟≤ 200ms上游响应时间、连接池是否打满连接数活跃连接 设置的上限连接泄漏、HttpClient 未正确关闭健康检查频率5-10 秒频率过高导致上游负载异常压测我一般用 wrk 或 JMeter。wrk 适合测吞吐量JMeter 适合测复杂业务场景。压测结果主要看 P99 和 P95平均值很容易被长尾拖累而失真。调优时先看线程池是否有大量阻塞任务再看 GC 频率最后才是代码逻辑。这三个方向覆盖了大多数网关性能瓶颈。5.4 一个调试技巧HTTP 响应头带上后端节点信息生产环境排障时最头疼的问题是不知道当前请求被转发到了哪台上游。一个非常实用的技巧在网关转发前写入一个自定义响应头例如X-Upstream: 192.168.1.10:8080这样 curl 或浏览器开发者工具就能直接看到后端节点。在压测调优时也能直观确认负载均衡是否生效而不需要去翻网关日志。这个头的实现只需一行代码在proxy()方法中回写响应前调用resp.setHeader(X-Upstream, target.getAddress())。不过安全起见这个响应头只应在测试环境开启生产环境可以通过配置关闭避免暴露内部拓扑结构。本文还有配套的精品资源点击获取