引言
在微服务架构中,反向代理是一个不可或缺的组件,它负责请求转发、负载均衡、安全过滤等关键功能。
通常我们会选择 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 {@Beanpublic 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)那么丰富,但在特定场景下,尤其是希望简化架构、统一技术栈的情况下,可以作为一种备选方案。