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