Spring Boot 内置反向代理(Undertow Proxy)高可用配置

发布于:2025-07-03 ⋅ 阅读:(23) ⋅ 点赞:(0)

引言

在微服务架构中,反向代理是一个不可或缺的组件,它负责请求转发、负载均衡、安全过滤等关键功能。

通常我们会选择 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)那么丰富,但在特定场景下,尤其是希望简化架构、统一技术栈的情况下,可以作为一种备选方案。


网站公告

今日签到

点亮在社区的每一天
去签到