SpringBoot利用Undertow實(shí)現(xiàn)高可用的反向代理配置
引言
在微服務(wù)架構(gòu)中,反向代理是一個(gè)不可或缺的組件,它負(fù)責(zé)請(qǐng)求轉(zhuǎn)發(fā)、負(fù)載均衡、安全過濾等關(guān)鍵功能。
通常我們會(huì)選擇 Nginx、HAProxy 等專業(yè)反向代理組件,但在某些場景下,使用 Spring Boot 內(nèi)置的反向代理功能可以簡化架構(gòu),減少運(yùn)維復(fù)雜度。
本文將介紹如何利用 Undertow 服務(wù)器的反向代理能力,實(shí)現(xiàn)高可用的反向代理配置。
Undertow 簡介
Undertow 是一個(gè)采用 Java 開發(fā)的靈活的高性能 Web 服務(wù)器,提供基于 NIO 的阻塞和非阻塞 API。
作為 Spring Boot 支持的內(nèi)嵌式服務(wù)器之一,它具有以下特點(diǎn):
- 輕量級(jí):核心僅依賴于 JBoss Logging 和 xnio
- 高性能:在多核系統(tǒng)上表現(xiàn)優(yōu)異
- 內(nèi)置反向代理:支持 HTTP、HTTPS、HTTP/2 代理
- 可擴(kuò)展:通過 Handler 鏈模式支持靈活擴(kuò)展
為什么選擇 Undertow 內(nèi)置反向代理
在某些場景下,使用 Undertow 內(nèi)置的反向代理功能比獨(dú)立部署 Nginx 等代理服務(wù)器更有優(yōu)勢:
1. 簡化架構(gòu):減少額外組件,降低部署復(fù)雜度
2. 統(tǒng)一技術(shù)棧:全 Java 技術(shù)棧,便于開發(fā)團(tuán)隊(duì)維護(hù)
3. 配置靈活:可通過代碼動(dòng)態(tài)調(diào)整代理規(guī)則
4. 節(jié)約資源:適合資源有限的環(huán)境,如邊緣計(jì)算場景
5. 集成監(jiān)控:與 Spring Boot 的監(jiān)控體系無縫集成
基礎(chǔ)配置
步驟 1:添加 Undertow 依賴
首先,確保 Spring Boot 項(xiàng)目使用 Undertow 作為嵌入式服務(wù)器:
<?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:創(chuàng)建 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); // 添加路由規(guī)則 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("創(chuàng)建代理客戶端失敗", 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) { // 根據(jù)實(shí)際情況調(diào)整 return new RequestLimitingHandler(1,1,next); } }
高可用配置
要實(shí)現(xiàn)真正的高可用反向代理,需要考慮以下幾個(gè)關(guān)鍵方面:
1. 負(fù)載均衡策略
Undertow 提供多種負(fù)載均衡策略,可以根據(jù)需求選擇:
@Bean public LoadBalancingProxyClient loadBalancingProxyClient() { LoadBalancingProxyClient loadBalancer = new LoadBalancingProxyClient(); // 配置負(fù)載均衡策略 loadBalancer.setRouteParsingStrategy(RouteParsingStrategy.RANKED); loadBalancer.setConnectionsPerThread(20); // 添加后端服務(wù)器 loadBalancer.addHost(new URI("http://backend1:8080")); loadBalancer.addHost(new URI("http://backend2:8080")); loadBalancer.addHost(new URI("http://backend3:8080")); // 設(shè)置會(huì)話親和性(可選) loadBalancer.addSessionCookieName("JSESSIONID"); return loadBalancer; }
2. 健康檢查與自動(dòng)故障轉(zhuǎn)移
實(shí)現(xiàn)定期健康檢查,自動(dòng)剔除不健康節(jié)點(diǎn):
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("后端服務(wù) {} 狀態(tài)正常,已添加到負(fù)載均衡", server); } else { // 服務(wù)不健康,從負(fù)載均衡器中移除 loadBalancer.removeHost(server); log.warn("后端服務(wù) {} 狀態(tài)異常,已從負(fù)載均衡中移除", server); } } catch (Exception e) { // 連接異常,從負(fù)載均衡器中移除 loadBalancer.removeHost(server); log.error("后端服務(wù) {} 連接異常: {}", server, e.getMessage()); } } } }
3. 集群高可用
為確保被代理服務(wù)的高可用,可配置多個(gè)代理實(shí)例:
user: backends: "http://127.0.0.1:8081,http://127.0.0.2:8081"
性能優(yōu)化
要獲得最佳性能,需要調(diào)整 Undertow 的相關(guān)參數(shù)(需要根據(jù)項(xiàng)目實(shí)際情況進(jìn)行測試調(diào)整):
server: undertow: threads: io: 8 # IO線程數(shù),建議設(shè)置為CPU核心數(shù) worker: 64 # 工作線程數(shù),IO線程數(shù)的8倍 buffer-size: 16384 # 緩沖區(qū)大小 direct-buffers: true # 使用直接緩沖區(qū) max-http-post-size: 10485760 # 最大POST大小 max-parameters: 2000 # 最大參數(shù)數(shù)量 max-headers: 200 # 最大請(qǐng)求頭數(shù)量 max-cookies: 200 # 最大Cookie數(shù)量
連接池優(yōu)化
@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; }
安全強(qiáng)化
反向代理需要考慮安全性,可以添加以下配置:
1. 請(qǐng)求頭過濾與重寫
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. 請(qǐng)求限流
private HttpHandler createRateLimitingHandler(HttpHandler next) { return new RequestLimitingHandler(100,next); }
實(shí)際案例:某系統(tǒng) API 網(wǎng)關(guān)
以一個(gè)電商系統(tǒng)為例,展示 Undertow 反向代理的實(shí)際應(yīng)用:
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 { // 用戶服務(wù)代理 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")); // 商品服務(wù)代理 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")); // 訂單服務(wù)代理 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")); // 路由規(guī)則 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(); } }
總結(jié)
Spring Boot 內(nèi)置的 Undertow 反向代理功能為微服務(wù)架構(gòu)提供了一種輕量級(jí)的代理解決方案。
雖然功能上可能不如專業(yè)的反向代理服務(wù)器(如 Nginx)那么豐富,但在特定場景下,尤其是希望簡化架構(gòu)、統(tǒng)一技術(shù)棧的情況下,可以作為一種備選方案。
以上就是SpringBoot利用Undertow實(shí)現(xiàn)高可用的反向代理配置的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Undertow反向代理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot WebSocket連接報(bào)no mapping for GE
文章描述了一個(gè)在調(diào)試WebSocket連接時(shí)遇到的`nomappingforGET`異常問題,并提供了問題解決的方法,包括檢查WebSocket注解和補(bǔ)充相關(guān)配置,此外,還特別提到了在使用Nginx轉(zhuǎn)發(fā)WebSocket時(shí)所需的配置2025-02-02SpringBoot結(jié)合mockito測試實(shí)戰(zhàn)
與集成測試將系統(tǒng)作為一個(gè)整體測試不同,單元測試更應(yīng)該專注于某個(gè)類。所以當(dāng)被測試類與外部類有依賴的時(shí)候,尤其是與數(shù)據(jù)庫相關(guān)的這種費(fèi)時(shí)且有狀態(tài)的類,很難做單元測試。但好在可以通過“Mockito”這種仿真框架來模擬這些比較費(fèi)時(shí)的類,從而專注于測試某個(gè)類內(nèi)部的邏輯2022-11-11Springboot接收文件與發(fā)送文件實(shí)例教程
最近工作中遇到個(gè)需求,springboot簡單的上傳文檔或者圖片,并且進(jìn)行操作,操作完后進(jìn)行保存指定路徑,下面這篇文章主要給大家介紹了關(guān)于Springboot接收文件與發(fā)送文件的相關(guān)資料,需要的朋友可以參考下2023-05-05關(guān)于Spring?Data?Jpa?自定義方法實(shí)現(xiàn)問題
這篇文章主要介紹了關(guān)于Spring?Data?Jpa?自定義方法實(shí)現(xiàn)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12Java countDownLatch如何實(shí)現(xiàn)多線程任務(wù)阻塞等待
這篇文章主要介紹了Java countDownLatch如何實(shí)現(xiàn)多線程任務(wù)阻塞等待,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10java實(shí)現(xiàn)微信公眾平臺(tái)自定義菜單的創(chuàng)建示例
這篇文章主要介紹了java實(shí)現(xiàn)微信公眾平臺(tái)自定義菜單的創(chuàng)建示例,需要的朋友可以參考下2014-04-04