SpringBoot利用Undertow实现高可用的反向代理配置
作者:风象南
引言
在微服务架构中,反向代理是一个不可或缺的组件,它负责请求转发、负载均衡、安全过滤等关键功能。
通常我们会选择 Nginx、HAProxy 等专业反向代理组件,但在某些场景下,使用 Spring Boot 内置的反向代理功能可以简化架构,减少运维复杂度。
本文将介绍如何利用 Undertow 服务器的反向代理能力,实现高可用的反向代理配置。
Undertow 简介
Undertow 是一个采用 Java 开发的灵活的高性能 Web 服务器,提供基于 NIO 的阻塞和非阻塞 API。
作为 Spring Boot 支持的内嵌式服务器之一,它具有以下特点:
- 轻量级:核心仅依赖于 JBoss Logging 和 xnio
- 高性能:在多核系统上表现优异
- 内置反向代理:支持 HTTP、HTTPS、HTTP/2 代理
- 可扩展:通过 Handler 链模式支持灵活扩展
为什么选择 Undertow 内置反向代理
在某些场景下,使用 Undertow 内置的反向代理功能比独立部署 Nginx 等代理服务器更有优势:
1. 简化架构:减少额外组件,降低部署复杂度
2. 统一技术栈:全 Java 技术栈,便于开发团队维护
3. 配置灵活:可通过代码动态调整代理规则
4. 节约资源:适合资源有限的环境,如边缘计算场景
5. 集成监控:与 Spring Boot 的监控体系无缝集成
基础配置
步骤 1:添加 Undertow 依赖
首先,确保 Spring Boot 项目使用 Undertow 作为嵌入式服务器:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.4.5</version> <relativePath/> </parent> <groupId>demo</groupId> <artifactId>springboot-undertow-proxy</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.32</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>21</source> <target>21</target> <encoding>utf-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>3.2.0</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
步骤 2:创建 Undertow 配置类
package com.example.config; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.RequestLimitingHandler; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.server.handlers.proxy.LoadBalancingProxyClient; import io.undertow.server.handlers.proxy.ProxyHandler; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.xnio.OptionMap; import java.net.URI; @Configuration public class UndertowProxyConfig { @Bean @ConditionalOnProperty(name = "user.enabled", havingValue = "false", matchIfMissing = true) public WebServerFactoryCustomizer<UndertowServletWebServerFactory> undertowProxyCustomizer() { return factory -> factory.addDeploymentInfoCustomizers(deploymentInfo -> { deploymentInfo.addInitialHandlerChainWrapper(handler -> { PathHandler pathHandler = Handlers.path(handler); // 配置代理路由 HttpHandler handler1 = createProxyClient("http://127.0.0.1:8081/user"); HttpHandler handler2 = createProxyClient("http://127.0.0.2:8081/user/users2"); handler1 = secureProxyHandler(handler1); handler1 = createRateLimitingHandler(handler1); // 添加路由规则 pathHandler.addPrefixPath("/user", handler1); pathHandler.addPrefixPath("/user/users2", handler2); return pathHandler; }); }); } private HttpHandler createProxyClient(String targetUrl) { try { URI uri = new URI(targetUrl); LoadBalancingProxyClient proxyClient = new LoadBalancingProxyClient(); proxyClient.addHost(uri); proxyClient .setConnectionsPerThread(20) .setMaxQueueSize(10) .setSoftMaxConnectionsPerThread(20) .setProblemServerRetry(5) .setTtl(30000); return ProxyHandler.builder() .setProxyClient(proxyClient) .setMaxRequestTime(30000) .setRewriteHostHeader(false) .setReuseXForwarded(true) .build(); } catch (Exception e) { throw new RuntimeException("创建代理客户端失败", e); } } private HttpHandler secureProxyHandler(HttpHandler proxyHandler) { return exchange -> { // 移除敏感头部 HeaderMap headers = exchange.getRequestHeaders(); headers.remove("X-Forwarded-Server"); // 添加安全头部 exchange.getResponseHeaders().add(new HttpString("X-XSS-Protection"), "1; mode=block"); exchange.getResponseHeaders().add(new HttpString("X-Content-Type-Options"), "nosniff"); exchange.getResponseHeaders().add(new HttpString("X-Frame-Options"), "DENY"); // 添加代理信息 headers.add(new HttpString("X-Forwarded-For"), exchange.getSourceAddress().getAddress().getHostAddress()); headers.add(new HttpString("X-Forwarded-Proto"), exchange.getRequestScheme()); headers.add(new HttpString("X-Forwarded-Host"), exchange.getHostName()); proxyHandler.handleRequest(exchange); }; } private HttpHandler createRateLimitingHandler(HttpHandler next) { // 根据实际情况调整 return new RequestLimitingHandler(1,1,next); } }
高可用配置
要实现真正的高可用反向代理,需要考虑以下几个关键方面:
1. 负载均衡策略
Undertow 提供多种负载均衡策略,可以根据需求选择:
@Bean public LoadBalancingProxyClient loadBalancingProxyClient() { LoadBalancingProxyClient loadBalancer = new LoadBalancingProxyClient(); // 配置负载均衡策略 loadBalancer.setRouteParsingStrategy(RouteParsingStrategy.RANKED); loadBalancer.setConnectionsPerThread(20); // 添加后端服务器 loadBalancer.addHost(new URI("http://backend1:8080")); loadBalancer.addHost(new URI("http://backend2:8080")); loadBalancer.addHost(new URI("http://backend3:8080")); // 设置会话亲和性(可选) loadBalancer.addSessionCookieName("JSESSIONID"); return loadBalancer; }
2. 健康检查与自动故障转移
实现定期健康检查,自动剔除不健康节点:
package com.example.config; import io.undertow.server.handlers.proxy.LoadBalancingProxyClient; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.ResponseEntity; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @Component @ConditionalOnProperty(name = "user.enabled", havingValue = "false", matchIfMissing = true) @Slf4j public class BackendHealthMonitor { private final LoadBalancingProxyClient loadBalancer; private final List<URI> backendServers; private final RestTemplate restTemplate; public BackendHealthMonitor(@Value("#{'${user.backends}'.split(',')}") String[] backends, LoadBalancingProxyClient loadBalancer) throws URISyntaxException { this.loadBalancer = loadBalancer; this.restTemplate = new RestTemplate(); this.backendServers = Arrays.stream(backends) .map(url -> { try { return new URI(url); } catch (URISyntaxException e) { throw new RuntimeException(e); } }) .collect(Collectors.toList()); } @Scheduled(fixedDelay = 10000) // 每10秒检查一次 public void checkBackendHealth() { for (URI server : backendServers) { try { String healthUrl = server.getScheme() + "://" + server.getHost() + ":" + server.getPort() + "/health"; ResponseEntity<String> response = restTemplate.getForEntity(healthUrl, String.class); if (response.getStatusCode().is2xxSuccessful()) { loadBalancer.addHost(server); log.info("后端服务 {} 状态正常,已添加到负载均衡", server); } else { // 服务不健康,从负载均衡器中移除 loadBalancer.removeHost(server); log.warn("后端服务 {} 状态异常,已从负载均衡中移除", server); } } catch (Exception e) { // 连接异常,从负载均衡器中移除 loadBalancer.removeHost(server); log.error("后端服务 {} 连接异常: {}", server, e.getMessage()); } } } }
3. 集群高可用
为确保被代理服务的高可用,可配置多个代理实例:
user: backends: "http://127.0.0.1:8081,http://127.0.0.2:8081"
性能优化
要获得最佳性能,需要调整 Undertow 的相关参数(需要根据项目实际情况进行测试调整):
server: undertow: threads: io: 8 # IO线程数,建议设置为CPU核心数 worker: 64 # 工作线程数,IO线程数的8倍 buffer-size: 16384 # 缓冲区大小 direct-buffers: true # 使用直接缓冲区 max-http-post-size: 10485760 # 最大POST大小 max-parameters: 2000 # 最大参数数量 max-headers: 200 # 最大请求头数量 max-cookies: 200 # 最大Cookie数量
连接池优化
@Bean public UndertowServletWebServerFactory undertowFactory() { UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory(); factory.addBuilderCustomizers(builder -> { builder.setSocketOption(Options.KEEP_ALIVE, true) .setSocketOption(Options.TCP_NODELAY, true) .setSocketOption(Options.REUSE_ADDRESSES, true) .setSocketOption(Options.BACKLOG, 10000) .setServerOption(UndertowOptions.MAX_ENTITY_SIZE, 16 * 1024 * 1024L) .setServerOption(UndertowOptions.IDLE_TIMEOUT, 60 * 1000) .setServerOption(UndertowOptions.REQUEST_PARSE_TIMEOUT, 30 * 1000) .setServerOption(UndertowOptions.NO_REQUEST_TIMEOUT, 60 * 1000) .setServerOption(UndertowOptions.MAX_CONCURRENT_REQUESTS_PER_CONNECTION, 200); }); return factory; }
安全强化
反向代理需要考虑安全性,可以添加以下配置:
1. 请求头过滤与重写
private HttpHandler secureProxyHandler(HttpHandler proxyHandler) { return exchange -> { // 移除敏感头部 HeaderMap headers = exchange.getRequestHeaders(); headers.remove("X-Forwarded-Server"); // 添加安全头部 exchange.getResponseHeaders().add(new HttpString("X-XSS-Protection"), "1; mode=block"); exchange.getResponseHeaders().add(new HttpString("X-Content-Type-Options"), "nosniff"); exchange.getResponseHeaders().add(new HttpString("X-Frame-Options"), "DENY"); // 添加代理信息 headers.add(new HttpString("X-Forwarded-For"), exchange.getSourceAddress().getAddress().getHostAddress()); headers.add(new HttpString("X-Forwarded-Proto"), exchange.getRequestScheme()); headers.add(new HttpString("X-Forwarded-Host"), exchange.getHostName()); proxyHandler.handleRequest(exchange); }; }
2. 请求限流
private HttpHandler createRateLimitingHandler(HttpHandler next) { return new RequestLimitingHandler(100,next); }
实际案例:某系统 API 网关
以一个电商系统为例,展示 Undertow 反向代理的实际应用:
package com.example.config; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.proxy.LoadBalancingProxyClient; import io.undertow.server.handlers.proxy.ProxyHandler; import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.net.URI; @Configuration public class EcommerceProxyConfig { @Bean public WebServerFactoryCustomizer<UndertowServletWebServerFactory> ecommerceProxyCustomizer() { return factory -> factory.addDeploymentInfoCustomizers(deploymentInfo -> { deploymentInfo.addInitialHandlerChainWrapper(handler -> { PathHandler pathHandler = Handlers.path(handler); try { // 用户服务代理 LoadBalancingProxyClient userServiceClient = new LoadBalancingProxyClient(); userServiceClient.addHost(new URI("http://user-service-1:8080/api/users")); userServiceClient.addHost(new URI("http://user-service-2:8080/api/users")); // 商品服务代理 LoadBalancingProxyClient productServiceClient = new LoadBalancingProxyClient(); productServiceClient.addHost(new URI("http://product-service-1:8080/api/products")); productServiceClient.addHost(new URI("http://product-service-2:8080/api/products")); // 订单服务代理 LoadBalancingProxyClient orderServiceClient = new LoadBalancingProxyClient(); orderServiceClient.addHost(new URI("http://order-service-1:8080/api/orders")); orderServiceClient.addHost(new URI("http://order-service-2:8080/api/orders")); // 路由规则 pathHandler.addPrefixPath("/api/users", createProxyHandler(userServiceClient)); pathHandler.addPrefixPath("/api/products", createProxyHandler(productServiceClient)); pathHandler.addPrefixPath("/api/orders", createProxyHandler(orderServiceClient)); return pathHandler; }catch (Exception e){ throw new RuntimeException(e); } }); }); } private HttpHandler createProxyHandler(LoadBalancingProxyClient client) { return ProxyHandler.builder() .setProxyClient(client) .setMaxRequestTime(30000) .setRewriteHostHeader(true) .build(); } }
总结
Spring Boot 内置的 Undertow 反向代理功能为微服务架构提供了一种轻量级的代理解决方案。
虽然功能上可能不如专业的反向代理服务器(如 Nginx)那么丰富,但在特定场景下,尤其是希望简化架构、统一技术栈的情况下,可以作为一种备选方案。
以上就是SpringBoot利用Undertow实现高可用的反向代理配置的详细内容,更多关于SpringBoot Undertow反向代理的资料请关注脚本之家其它相关文章!