引言
在微服務架構中,反向代理是一個不可或缺的組件,它負責請求轉發、負載均衡、安全過濾等關鍵功能。
通常我們會選擇 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)那么豐富,但在特定場景下,尤其是希望簡化架構、統一技術棧的情況下,可以作為一種備選方案。