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

