Jetty vs Tomcat:Spring Boot应用场景最佳选择

你的Spring Boot应用还在用默认的Tomcat?面对高并发场景,Jetty可能是更好的选择!
为什么Jetty能做得更好? 这要从两个服务器的架构差异说起。

一、Jetty vs Tomcat:全面对比

1. 架构设计对比

Jetty架构
Server
Connectors
Selectors
Acceptors
线程池 QueuedThreadPool
HTTP通道
执行线程
Handler链
Servlet容器
Tomcat架构
Connector
HTTP/1.1
HTTP/2
AJP
线程池 Thread Pool
Worker Threads
Servlet容器

核心差异表

对比维度 Tomcat Jetty 优势分析
架构理念 传统的Servlet容器 基于Handler的异步架构 Jetty更轻量、灵活
线程模型 每个请求分配一个线程 基于NIO的Selector模型 Jetty更适合高并发
内存占用 较高(完整Servlet容器) 较低(模块化设计) Jetty启动更快,内存更省
配置方式 XML配置为主 编程式配置为主 Jetty配置更灵活
热部署 支持,但较重 优秀的热部署支持 Jetty开发体验更好

2. 性能特性对比

java 复制代码
// 性能测试对比工具
@Component
@Slf4j
public class ServerPerformanceComparator {
    
    /**
     * 对比两种服务器的关键性能指标
     */
    public void comparePerformance() {
        Map<String, ServerMetrics> metrics = new LinkedHashMap<>();
        
        // Tomcat性能特征
        metrics.put("Tomcat", new ServerMetrics(
            "Apache Tomcat 10.1.x",
            "BIO/NIO可选", 
            200,      // 默认最大线程数
            8192,     // 默认最大连接数
            10485760, // 10MB header限制
            true,     // 支持AJP
            "中等",    // 内存占用
            "优秀",    // 稳定性
            "适合传统Web应用"
        ));
        
        // Jetty性能特征
        metrics.put("Jetty", new ServerMetrics(
            "Eclipse Jetty 12.0.x",
            "NIO/HTTP2优先", 
            250,      // 默认最大线程数
            20000,    // 默认支持更高并发
            16384,    // 16KB header限制
            false,    // 不支持AJP
            "较低",    // 内存占用
            "优秀",    // 稳定性
            "适合高并发、实时应用"
        ));
        
        log.info("🚀 服务器性能对比报告");
        metrics.forEach((name, metric) -> {
            log.info("\n{} 性能特征:", name);
            log.info("  - 线程模型: {}", metric.threadModel());
            log.info("  - 默认最大线程: {}", metric.defaultMaxThreads());
            log.info("  - 默认最大连接: {}", metric.defaultMaxConnections());
            log.info("  - 内存占用: {}", metric.memoryUsage());
            log.info("  - 适用场景: {}", metric.suitableScenarios());
        });
    }
    
    record ServerMetrics(
        String version,
        String threadModel,
        int defaultMaxThreads,
        int defaultMaxConnections,
        int maxHttpHeaderSize,
        boolean supportAjp,
        String memoryUsage,
        String stability,
        String suitableScenarios
    ) {}
}

运行结果

复制代码
🚀 服务器性能对比报告

Tomcat 性能特征:
  - 线程模型: BIO/NIO可选
  - 默认最大线程: 200
  - 默认最大连接: 8192
  - 内存占用: 中等
  - 适用场景: 适合传统Web应用

Jetty 性能特征:
  - 线程模型: NIO/HTTP2优先
  - 默认最大线程: 250
  - 默认最大连接: 20000
  - 内存占用: 较低
  - 适用场景: 适合高并发、实时应用

二、Spring Boot集成 Jetty

1. 从Tomcat切换到Jetty

1.1 修改Maven依赖

Tomcat默认配置(Spring Boot默认):

xml 复制代码
<!-- Spring Boot默认包含Tomcat -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!-- 默认内嵌Tomcat -->
</dependency>

切换到Jetty

xml 复制代码
<!-- pom.xml -->
<dependencies>
    <!-- 1. 排除Tomcat -->
    <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>
    
    <!-- 2. 添加Jetty依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
    
    <!-- 3. 其他依赖保持不变 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
    <!-- 数据库、Redis等业务依赖 -->
</dependencies>
1.2 验证切换结果
java 复制代码
@Component
@Slf4j
public class ServerTypeDetector implements ApplicationRunner {
    
    @Override
    public void run(ApplicationArguments args) {
        // 检测当前使用的服务器
        String serverInfo = detectServer();
        log.info("🎯 当前服务器: {}", serverInfo);
        
        // 打印服务器详细信息
        printServerDetails();
    }
    
    private String detectServer() {
        try {
            // 通过类路径检测
            Class.forName("org.eclipse.jetty.server.Server");
            return "Jetty Server";
        } catch (ClassNotFoundException e1) {
            try {
                Class.forName("org.apache.catalina.startup.Tomcat");
                return "Tomcat Server";
            } catch (ClassNotFoundException e2) {
                return "Unknown Server";
            }
        }
    }
    
    private void printServerDetails() {
        // 获取服务器相关Bean
        Map<String, String> serverBeans = new LinkedHashMap<>();
        
        try {
            // Jetty特定Bean
            Class<?> jettyServerClass = Class.forName("org.eclipse.jetty.server.Server");
            serverBeans.put("Server Type", "Jetty");
            
            // 获取Jetty版本
            Package jettyPackage = jettyServerClass.getPackage();
            serverBeans.put("Jetty Version", jettyPackage.getImplementationVersion());
            
        } catch (ClassNotFoundException e) {
            try {
                // Tomcat特定Bean
                Class.forName("org.apache.catalina.startup.Tomcat");
                serverBeans.put("Server Type", "Tomcat");
            } catch (ClassNotFoundException ex) {
                serverBeans.put("Server Type", "Unknown");
            }
        }
        
        // 打印启动日志
        log.info("📊 服务器详细信息:");
        serverBeans.forEach((key, value) -> 
            log.info("  {}: {}", key, value != null ? value : "N/A"));
    }
}

启动日志对比

Tomcat启动日志

复制代码
2024-05-20 10:00:00.000  INFO 12345 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080
2024-05-20 10:00:00.100  INFO 12345 --- [main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]

Jetty启动日志

复制代码
2024-05-20 10:00:00.000  INFO 12345 --- [main] o.s.b.w.embedded.jetty.JettyWebServer    : Jetty started on port 8080
2024-05-20 10:00:00.050  INFO 12345 --- [main] org.eclipse.jetty.server.Server         : jetty-12.0.8

2. Jetty基础配置

2.1 基础YAML配置
yaml 复制代码
# application.yml
server:
  port: 8080
  
  # Jetty特有配置
  jetty:
    # 线程池配置
    threads:
      min: 10                    # 最小线程数
      max: 200                   # 最大线程数
      max-queue-capacity: 6000   # 队列容量
      idle-timeout: 60000        # 空闲超时(ms)
    
    # 连接器配置
    connection-idle-timeout: 30000  # 连接空闲超时
    
    # HTTP配置
    max-http-form-post-size: 2MB    # 最大表单提交大小
    max-http-header-size: 16KB      # HTTP头最大大小
    
    # 访问日志
    accesslog:
      enabled: true
      filename: ./logs/jetty-access.log
      retain-days: 30
    
    # 压缩配置
    compression:
      enabled: true
      min-gzip-size: 1024
    
  # SSL配置(如果需要)
  ssl:
    enabled: false
    key-store: classpath:keystore.p12
    key-store-password: changeme
    key-store-type: PKCS12
    key-alias: tomcat
    protocol: TLS
    enabled-protocols: TLSv1.2,TLSv1.3

# 与Tomcat配置对比
tomcat:
  # Tomcat对应配置(供参考)
  max-threads: 200
  max-connections: 8192
  connection-timeout: 20000
  max-http-form-post-size: 2MB
  max-http-header-size: 8KB  # Tomcat默认8KB,Jetty默认16KB
2.2 编程式配置
java 复制代码
@Configuration
@Slf4j
public class JettyServerConfig {
    
    /**
     * 自定义Jetty服务器配置
     * 与Tomcat的TomcatConnectorCustomizer对比
     */
    @Bean
    public ConfigurableServletWebServerFactory webServerFactory() {
        JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
        
        // 端口配置
        factory.setPort(8080);
        
        // 上下文路径
        factory.setContextPath("/api");
        
        // 添加自定义配置
        factory.addServerCustomizers(this::customizeJettyServer);
        
        // 添加连接器自定义
        factory.addConnectorCustomizers(this::customizeConnector);
        
        log.info("✅ Jetty服务器工厂配置完成");
        return factory;
    }
    
    /**
     * 自定义Jetty服务器
     * 对应Tomcat的TomcatServletWebServerFactory
     */
    private void customizeJettyServer(Server server) {
        // 1. 配置线程池
        QueuedThreadPool threadPool = (QueuedThreadPool) server.getThreadPool();
        threadPool.setMinThreads(20);
        threadPool.setMaxThreads(500);
        threadPool.setIdleTimeout(60000); // 60秒空闲超时
        threadPool.setDetailedDump(false);
        
        // 2. 配置服务器属性
        server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize", 2000000); // 2MB
        server.setAttribute("org.eclipse.jetty.server.Request.maxFormKeys", 1000);
        
        // 3. 添加统计处理器
        server.insertHandler(new StatisticsHandler());
        
        // 4. 配置Dump状态
        server.setDumpAfterStart(false);
        server.setDumpBeforeStop(false);
        
        log.info("🔧 Jetty服务器自定义完成: 线程池={}-{}", 
            threadPool.getMinThreads(), threadPool.getMaxThreads());
    }
    
    /**
     * 自定义连接器
     * 对应Tomcat的ConnectorCustomizer
     */
    private void customizeConnector(ServerConnector connector) {
        // 配置HTTP
        HttpConfiguration httpConfig = connector.getConnectionFactory(HttpConnectionFactory.class)
            .getHttpConfiguration();
        
        // 请求头配置
        httpConfig.setRequestHeaderSize(16384);    // 16KB
        httpConfig.setResponseHeaderSize(16384);   // 16KB
        httpConfig.setSendServerVersion(false);    // 隐藏服务器版本
        httpConfig.setSendDateHeader(true);        // 发送日期头
        
        // 连接器超时配置
        connector.setIdleTimeout(30000);           // 30秒空闲超时
        connector.setAcceptQueueSize(100);         // 接受队列大小
        
        // 配置SSL(如果需要)
        if (connector.getDefaultConnectionFactory() instanceof SslConnectionFactory) {
            customizeSslConnector(connector);
        }
        
        log.info("🔌 Jetty连接器配置完成: idleTimeout={}ms, acceptQueue={}", 
            connector.getIdleTimeout(), connector.getAcceptQueueSize());
    }
    
    /**
     * 与Tomcat配置对比示例
     */
    @Bean
    @ConditionalOnMissingClass("org.eclipse.jetty.server.Server")
    public ConfigurableServletWebServerFactory tomcatWebServerFactory() {
        // 这是Tomcat的对应配置,供对比参考
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        
        factory.addConnectorCustomizers(connector -> {
            // Tomcat连接器配置
            connector.setProperty("maxThreads", "200");
            connector.setProperty("acceptCount", "100");
            connector.setProperty("connectionTimeout", "20000");
            connector.setProperty("maxConnections", "8192");
        });
        
        return factory;
    }
}

3. 高级配置:线程池优化

java 复制代码
@Configuration
@Slf4j
public class JettyThreadPoolConfig {
    
    /**
     * 自定义Jetty线程池
     * Jetty使用QueuedThreadPool,Tomcat使用ThreadPoolExecutor
     */
    @Bean
    public QueuedThreadPool jettyThreadPool() {
        // 获取CPU核心数
        int processors = Runtime.getRuntime().availableProcessors();
        
        // 根据CPU核心数动态计算线程数
        int minThreads = Math.max(10, processors * 2);
        int maxThreads = Math.max(200, processors * 50);
        
        QueuedThreadPool threadPool = new QueuedThreadPool(maxThreads, minThreads, 60000);
        
        // 线程池配置
        threadPool.setName("jetty-worker");           // 线程名称前缀
        threadPool.setDetailedDump(false);            // 关闭详细dump
        threadPool.setReservedThreads(-1);            // 保留线程数
        threadPool.setThreadsPriority(Thread.NORM_PRIORITY);
        
        // 设置线程工厂(支持虚拟线程)
        threadPool.setThreadFactory(createThreadFactory());
        
        // 监控线程池状态
        monitorThreadPool(threadPool);
        
        log.info("🧵 Jetty线程池配置: min={}, max={}, idleTimeout=60s", 
            minThreads, maxThreads);
        
        return threadPool;
    }
    
    /**
     * 创建支持虚拟线程的线程工厂
     */
    private ThreadFactory createThreadFactory() {
        return r -> {
            Thread thread = new Thread(r);
            thread.setName("jetty-worker-" + thread.getId());
            thread.setDaemon(false);
            thread.setUncaughtExceptionHandler((t, e) -> 
                log.error("Jetty工作线程异常: {}", t.getName(), e));
            return thread;
        };
    }
    
    /**
     * 线程池监控
     */
    private void monitorThreadPool(QueuedThreadPool threadPool) {
        ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
        
        monitor.scheduleAtFixedRate(() -> {
            try {
                int threads = threadPool.getThreads();
                int idleThreads = threadPool.getIdleThreads();
                int busyThreads = threads - idleThreads;
                int queueSize = threadPool.getQueueSize();
                
                log.debug("📊 Jetty线程池状态: 总数={}, 忙碌={}, 空闲={}, 队列={}", 
                    threads, busyThreads, idleThreads, queueSize);
                
                // 预警:线程使用率过高
                double usageRate = (double) busyThreads / threadPool.getMaxThreads() * 100;
                if (usageRate > 80) {
                    log.warn("⚠️ Jetty线程池使用率过高: {}%", String.format("%.1f", usageRate));
                }
                
                // 预警:队列堆积
                if (queueSize > 1000) {
                    log.warn("⚠️ Jetty线程池队列堆积: {}", queueSize);
                }
                
            } catch (Exception e) {
                log.error("线程池监控异常", e);
            }
        }, 30, 30, TimeUnit.SECONDS); // 每30秒监控一次
    }
    
    /**
     * 与Tomcat线程池对比
     */
    @Bean
    @ConditionalOnProperty(name = "server.tomcat.enabled", havingValue = "true")
    public ThreadPoolExecutor tomcatThreadPool() {
        // Tomcat使用标准的ThreadPoolExecutor
        int processors = Runtime.getRuntime().availableProcessors();
        int corePoolSize = Math.max(10, processors * 2);
        int maxPoolSize = Math.max(200, processors * 50);
        
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            corePoolSize,
            maxPoolSize,
            60L, TimeUnit.SECONDS,  // 空闲超时
            new LinkedBlockingQueue<>(10000),  // 工作队列
            new CustomThreadFactory("tomcat-worker"),
            new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略
        );
        
        log.info("Tomcat线程池配置: core={}, max={}, queue=10000", 
            corePoolSize, maxPoolSize);
        
        return executor;
    }
    
    /**
     * 自定义线程工厂
     */
    static class CustomThreadFactory implements ThreadFactory {
        private final String namePrefix;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        
        CustomThreadFactory(String namePrefix) {
            this.namePrefix = namePrefix;
        }
        
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r, namePrefix + "-" + threadNumber.getAndIncrement());
            thread.setDaemon(false);
            return thread;
        }
    }
}

三、性能调优对比

1. 连接器配置优化

yaml 复制代码
# application.yml - Jetty优化配置
server:
  jetty:
    # 连接器配置
    connection:
      idle-timeout: 30000        # 30秒空闲超时
      stop-timeout: 30000        # 优雅停止超时
      so-linger-time: -1         # SO_LINGER
    
    # 选择器配置
    selectors: 2                 # 选择器线程数(通常=CPU核心数/2)
    acceptors: 1                 # 接受器线程数
    
    # 缓冲区配置
    output-buffer-size: 32768    # 32KB输出缓冲区
    request-header-size: 16384   # 16KB请求头
    response-header-size: 16384  # 16KB响应头
    
    # 解压缩配置
    decompression:
      enabled: true
      buffer-size: 8192

# 对应的Tomcat配置
server:
  tomcat:
    max-connections: 8192
    accept-count: 100
    connection-timeout: 20000
    max-keep-alive-requests: 100
    keep-alive-timeout: 20000
    max-http-request-header-size: 8192  # Tomcat默认8KB

2. 企业级Jetty配置

java 复制代码
@Configuration
@Slf4j
public class EnterpriseJettyConfig {
    
    @Value("${server.port:8080}")
    private int serverPort;
    
    @Value("${management.server.port:9090}")
    private int managementPort;
    
    /**
     * 企业级Jetty配置:多连接器、监控端点分离
     */
    @Bean
    public ConfigurableServletWebServerFactory jettyServletContainer() {
        JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
        
        // 主连接器(业务端口)
        factory.addServerCustomizers(server -> {
            // 创建业务连接器
            ServerConnector businessConnector = createBusinessConnector(server);
            server.addConnector(businessConnector);
            
            // 创建管理连接器(独立端口,只供内网访问)
            if (managementPort > 0 && managementPort != serverPort) {
                ServerConnector managementConnector = createManagementConnector(server);
                server.addConnector(managementConnector);
            }
            
            // 移除默认连接器
            removeDefaultConnectors(server);
        });
        
        return factory;
    }
    
    /**
     * 创建业务连接器
     */
    private ServerConnector createBusinessConnector(Server server) {
        // HTTP配置
        HttpConfiguration httpConfig = new HttpConfiguration();
        httpConfig.setSecureScheme("https");
        httpConfig.setSecurePort(serverPort);
        httpConfig.setSendServerVersion(false);
        httpConfig.setSendDateHeader(true);
        httpConfig.setRequestHeaderSize(16384);
        httpConfig.setResponseHeaderSize(16384);
        
        // 连接器配置
        ServerConnector connector = new ServerConnector(
            server,
            new DetectorConnectionFactory(new HttpConnectionFactory(httpConfig))
        );
        
        connector.setPort(serverPort);
        connector.setIdleTimeout(30000);
        connector.setAcceptQueueSize(100);
        connector.setName("business-http");
        
        // 连接器统计
        connector.addBean(new ConnectionStatistics());
        
        log.info("🔌 业务连接器创建: port={}, name={}", serverPort, connector.getName());
        return connector;
    }
    
    /**
     * 创建管理连接器(Actuator端点)
     */
    private ServerConnector createManagementConnector(Server server) {
        HttpConfiguration httpConfig = new HttpConfiguration();
        httpConfig.setSendServerVersion(false);
        httpConfig.setRequestHeaderSize(8192);
        httpConfig.setResponseHeaderSize(8192);
        
        ServerConnector connector = new ServerConnector(
            server,
            new HttpConnectionFactory(httpConfig)
        );
        
        connector.setPort(managementPort);
        connector.setIdleTimeout(10000);  // 管理端点超时更短
        connector.setAcceptQueueSize(10); // 队列更小
        connector.setName("management-http");
        
        // 只绑定到本地回环地址
        connector.setHost("127.0.0.1");
        
        log.info("🔧 管理连接器创建: port={}, host=127.0.0.1", managementPort);
        return connector;
    }
    
    /**
     * 移除默认连接器
     */
    private void removeDefaultConnectors(Server server) {
        Arrays.stream(server.getConnectors())
              .filter(connector -> connector.getPort() == serverPort)
              .findFirst()
              .ifPresent(connector -> {
                  server.removeConnector(connector);
                  log.info("移除默认连接器: port={}", connector.getPort());
              });
    }
    
    /**
     * 与Tomcat多连接器配置对比
     */
    @Bean
    @ConditionalOnProperty(name = "server.tomcat.multi-connector", havingValue = "true")
    public ConfigurableServletWebServerFactory tomcatMultiConnector() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        
        factory.addConnectorCustomizers(connector -> {
            // Tomcat多连接器需要更复杂的配置
            connector.setPort(serverPort);
            connector.setProperty("maxThreads", "200");
            connector.setProperty("acceptCount", "100");
        });
        
        // Tomcat添加第二个连接器
        factory.addAdditionalTomcatConnectors(createTomcatManagementConnector());
        
        return factory;
    }
    
    private Connector createTomcatManagementConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setPort(managementPort);
        connector.setProperty("maxThreads", "50");
        connector.setProperty("acceptCount", "10");
        connector.setProperty("address", "127.0.0.1");
        return connector;
    }
}

3. 监控与指标

java 复制代码
@Component
@Slf4j
public class JettyMetricsExporter {
    
    private final MeterRegistry meterRegistry;
    private final Server server;
    
    public JettyMetricsExporter(MeterRegistry meterRegistry, 
                               @Autowired(required = false) Server server) {
        this.meterRegistry = meterRegistry;
        this.server = server;
        
        if (server != null) {
            exportJettyMetrics();
        }
    }
    
    /**
     * 导出Jetty监控指标
     */
    private void exportJettyMetrics() {
        // 线程池指标
        ThreadPool threadPool = server.getThreadPool();
        if (threadPool instanceof QueuedThreadPool jettyThreadPool) {
            Gauge.builder("jetty.threads", jettyThreadPool, QueuedThreadPool::getThreads)
                .description("Jetty线程池当前线程数")
                .register(meterRegistry);
            
            Gauge.builder("jetty.threads.idle", jettyThreadPool, QueuedThreadPool::getIdleThreads)
                .description("Jetty线程池空闲线程数")
                .register(meterRegistry);
            
            Gauge.builder("jetty.threads.queue", jettyThreadPool, QueuedThreadPool::getQueueSize)
                .description("Jetty线程池队列大小")
                .register(meterRegistry);
        }
        
        // 连接器指标
        for (Connector connector : server.getConnectors()) {
            if (connector instanceof ServerConnector serverConnector) {
                String connectorName = serverConnector.getName();
                
                Gauge.builder("jetty.connections", serverConnector, ServerConnector::getConnectedEndPoints)
                    .tag("connector", connectorName)
                    .description("Jetty连接器当前连接数")
                    .register(meterRegistry);
                
                // 连接统计
                ConnectionStatistics stats = serverConnector.getBean(ConnectionStatistics.class);
                if (stats != null) {
                    Gauge.builder("jetty.connections.total", stats, ConnectionStatistics::getConnectionsTotal)
                        .tag("connector", connectorName)
                        .description("Jetty总连接数")
                        .register(meterRegistry);
                    
                    Gauge.builder("jetty.messages.in", stats, ConnectionStatistics::getMessagesIn)
                        .tag("connector", connectorName)
                        .description("Jetty接收消息数")
                        .register(meterRegistry);
                    
                    Gauge.builder("jetty.messages.out", stats, ConnectionStatistics::getMessagesOut)
                        .tag("connector", connectorName)
                        .description("Jetty发送消息数")
                        .register(meterRegistry);
                }
            }
        }
        
        log.info("📈 Jetty监控指标已注册到Micrometer");
    }
    
    /**
     * 与Tomcat监控对比
     */
    @Component
    @ConditionalOnBean(name = "tomcatServletWebServerFactory")
    @Slf4j
    static class TomcatMetricsExporter {
        
        private final MeterRegistry meterRegistry;
        
        public TomcatMetricsExporter(MeterRegistry meterRegistry) {
            this.meterRegistry = meterRegistry;
            exportTomcatMetrics();
        }
        
        private void exportTomcatMetrics() {
            // Tomcat通过JMX暴露指标
            try {
                MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
                
                // 线程池指标
                ObjectName threadPoolName = new ObjectName(
                    "Tomcat:type=ThreadPool,name=\"*\"");
                
                Set<ObjectName> threadPools = mBeanServer.queryNames(threadPoolName, null);
                for (ObjectName name : threadPools) {
                    String poolName = name.getKeyProperty("name");
                    
                    Gauge.builder("tomcat.threads.current", mBeanServer, 
                            server -> getAttribute(server, name, "currentThreadCount"))
                        .tag("pool", poolName)
                        .description("Tomcat当前线程数")
                        .register(meterRegistry);
                    
                    Gauge.builder("tomcat.threads.busy", mBeanServer,
                            server -> getAttribute(server, name, "currentThreadsBusy"))
                        .tag("pool", poolName)
                        .description("Tomcat忙碌线程数")
                        .register(meterRegistry);
                }
                
                log.info("📈 Tomcat监控指标已注册到Micrometer");
                
            } catch (Exception e) {
                log.warn("Tomcat监控指标注册失败", e);
            }
        }
        
        private Number getAttribute(MBeanServer server, ObjectName name, String attribute) {
            try {
                Object value = server.getAttribute(name, attribute);
                return value instanceof Number number ? number : 0;
            } catch (Exception e) {
                return 0;
            }
        }
    }
}

四、Jetty 特有功能解析

1. Jetty的Handler架构

java 复制代码
@Configuration
@Slf4j
public class JettyHandlerConfig {
    
    /**
     * Jetty核心:Handler链配置
     * Jetty通过Handler链处理请求,Tomcat通过Valve链
     */
    @Bean
    public HandlerCollection jettyHandlers() {
        HandlerCollection handlers = new HandlerCollection();
        
        // 1. 请求日志Handler
        handlers.addHandler(createRequestLogHandler());
        
        // 2. 统计Handler
        handlers.addHandler(createStatisticsHandler());
        
        // 3. 安全Header Handler
        handlers.addHandler(createSecurityHeaderHandler());
        
        // 4. GZip压缩Handler
        handlers.addHandler(createGzipHandler());
        
        // 5. 默认Handler(最后)
        handlers.addHandler(new DefaultHandler());
        
        log.info("🔗 Jetty Handler链配置完成");
        return handlers;
    }
    
    /**
     * 请求日志Handler
     * 对应Tomcat的AccessLogValve
     */
    private RequestLogHandler createRequestLogHandler() {
        CustomRequestLogWriter logWriter = new CustomRequestLogWriter();
        logWriter.setFilename("./logs/jetty-request.yyyy_mm_dd.log");
        logWriter.setRetainDays(30);
        logWriter.setAppend(true);
        
        // 自定义日志格式
        String logFormat = CustomRequestLog.EXTENDED_NCSA_FORMAT + " %{Referer}i %{User-Agent}i";
        
        CustomRequestLog requestLog = new CustomRequestLog(logWriter, logFormat);
        RequestLogHandler handler = new RequestLogHandler();
        handler.setRequestLog(requestLog);
        
        return handler;
    }
    
    /**
     * 统计Handler
     * 对应Tomcat的StatValve
     */
    private StatisticsHandler createStatisticsHandler() {
        StatisticsHandler stats = new StatisticsHandler();
        stats.setHandler(new DefaultHandler());
        
        // 定时打印统计信息
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(() -> {
            if (stats.getStatsOnMs() > 0) {
                log.debug("📊 Jetty请求统计: requests={}, time={}ms, mean={}ms", 
                    stats.getRequests(),
                    stats.getRequestTimeTotal(),
                    stats.getRequestTimeMean());
            }
        }, 60, 60, TimeUnit.SECONDS);
        
        return stats;
    }
    
    /**
     * 安全Header Handler
     */
    private HandlerWrapper createSecurityHeaderHandler() {
        return new HandlerWrapper() {
            @Override
            public void handle(String target, Request baseRequest, 
                              HttpServletRequest request, HttpServletResponse response) 
                    throws IOException, ServletException {
                
                // 添加安全相关HTTP头
                response.setHeader("X-Content-Type-Options", "nosniff");
                response.setHeader("X-Frame-Options", "DENY");
                response.setHeader("X-XSS-Protection", "1; mode=block");
                response.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
                
                // 继续处理链
                if (getHandler() != null) {
                    getHandler().handle(target, baseRequest, request, response);
                }
            }
        };
    }
    
    /**
     * GZip压缩Handler
     * Jetty内置支持,Tomcat需要配置compression
     */
    private GzipHandler createGzipHandler() {
        GzipHandler gzip = new GzipHandler();
        
        // 包含的MIME类型
        gzip.setIncludedMimeTypes("text/html", "text/xml", "text/plain", 
                                 "text/css", "text/javascript", "application/javascript", 
                                 "application/json", "application/xml");
        
        // 压缩级别
        gzip.setMinGzipSize(1024);  // 1KB以上才压缩
        
        // 排除的User-Agent
        gzip.setExcludedAgentPatterns(".*MSIE 6.*");
        
        return gzip;
    }
    
    /**
     * 与Tomcat Valve链对比
     */
    @Bean
    @ConditionalOnProperty(name = "server.tomcat.custom-valves", havingValue = "true")
    public TomcatServletWebServerFactory tomcatWithValves() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        
        factory.addContextValves(
            // AccessLogValve
            new org.apache.catalina.valves.AccessLogValve(),
            
            // 统计Valve
            new org.apache.catalina.valves.StatsValve(),
            
            // 远程IP Valve
            new org.apache.catalina.valves.RemoteIpValve()
        );
        
        return factory;
    }
}

2. HTTP/2和HTTP/3支持

java 复制代码
@Configuration
@ConditionalOnProperty(name = "server.http2.enabled", havingValue = "true")
@Slf4j
public class JettyHttp2Config {
    
    /**
     * Jetty对HTTP/2和HTTP/3的卓越支持
     * 相比Tomcat,Jetty的HTTP/2实现更成熟
     */
    @Bean
    public ConfigurableServletWebServerFactory http2EnabledServer() {
        JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
        
        factory.addServerCustomizers(server -> {
            // 获取HTTP连接器
            for (Connector connector : server.getConnectors()) {
                if (connector instanceof ServerConnector serverConnector) {
                    configureHttp2(serverConnector);
                }
            }
        });
        
        return factory;
    }
    
    private void configureHttp2(ServerConnector connector) {
        // 检测是否支持HTTP/2
        ConnectionFactory[] factories = connector.getConnectionFactories();
        for (int i = 0; i < factories.length; i++) {
            if (factories[i] instanceof HttpConnectionFactory) {
                // 替换为支持HTTP/2的工厂
                factories[i] = createHttp2ConnectionFactory();
                connector.setConnectionFactories(factories);
                break;
            }
        }
        
        // 配置ALPN(应用层协议协商)
        connector.addBean(new ALPNServerConnectionFactory());
        
        // 配置SSL(HTTP/2必须使用SSL)
        SslConnectionFactory ssl = new SslConnectionFactory();
        connector.addBean(ssl, 0); // 添加到连接器链的开始
        
        log.info("🚀 HTTP/2已启用");
    }
    
    private HttpConnectionFactory createHttp2ConnectionFactory() {
        HttpConfiguration httpConfig = new HttpConfiguration();
        httpConfig.setSendServerVersion(false);
        httpConfig.setSecureScheme("https");
        
        // HTTP/1.1连接工厂
        HttpConnectionFactory http1 = new HttpConnectionFactory(httpConfig);
        
        // HTTP/2连接工厂
        HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpConfig);
        
        // 检测连接工厂,支持协议协商
        return new DetectorConnectionFactory(http2, http1);
    }
    
    /**
     * 与Tomcat HTTP/2配置对比
     */
    @Bean
    @ConditionalOnProperty(name = "server.tomcat.http2.enabled", havingValue = "true")
    public ConfigurableServletWebServerFactory tomcatHttp2() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        
        factory.addConnectorCustomizers(connector -> {
            // Tomcat启用HTTP/2
            connector.addUpgradeProtocol(new org.apache.coyote.http2.Http2Protocol());
            
            // 必须配置SSL
            connector.setScheme("https");
            connector.setSecure(true);
        });
        
        return factory;
    }
}

五、迁移指南:Tomcat 到 Jetty

1. 依赖和配置迁移

yaml 复制代码
# migration-guide.yml
# 从Tomcat迁移到Jetty的配置对比

# ========== Tomcat配置 ==========
tomcat-config:
  server:
    tomcat:
      # 线程池配置
      max-threads: 200
      min-spare-threads: 10
      
      # 连接器配置
      max-connections: 8192
      accept-count: 100
      connection-timeout: 20000
      
      # HTTP配置
      max-http-header-size: 8192
      max-http-post-size: 2097152
      
      # 访问日志
      accesslog:
        enabled: true
        pattern: "%t %a %r %s %D"
        
      # 压缩
      compression: on
      compression-min-size: 2048

# ========== Jetty对应配置 ==========
jetty-config:
  server:
    jetty:
      # 线程池配置
      threads:
        max: 200
        min: 10
        
      # 连接器配置
      connection-idle-timeout: 20000
      accept-queue-size: 100
      
      # HTTP配置
      max-http-header-size: 8192
      max-http-form-post-size: 2MB
      
      # 访问日志
      accesslog:
        enabled: true
        format: "%t %a %r %s %D"
        
      # 压缩
      compression:
        enabled: true
        min-gzip-size: 2048

2. 迁移检查工具

java 复制代码
@Component
@Slf4j
public class TomcatToJettyMigrationChecker {
    
    private final ApplicationContext context;
    
    public TomcatToJettyMigrationChecker(ApplicationContext context) {
        this.context = context;
    }
    
    @EventListener(ApplicationReadyEvent.class)
    public void checkMigrationReadiness() {
        log.info("🔍 开始Tomcat到Jetty迁移检查...");
        
        List<MigrationIssue> issues = new ArrayList<>();
        
        // 检查1: 依赖冲突
        checkDependencyConflicts(issues);
        
        // 检查2: 特定于Tomcat的配置
        checkTomcatSpecificConfigs(issues);
        
        // 检查3: 自定义Tomcat组件
        checkCustomTomcatComponents(issues);
        
        // 检查4: WebSocket配置
        checkWebSocketConfig(issues);
        
        // 检查5: SSL配置
        checkSslConfig(issues);
        
        // 输出检查结果
        if (issues.isEmpty()) {
            log.info("✅ 迁移检查通过,可以安全切换到Jetty");
        } else {
            log.warn("⚠️ 发现{}个迁移问题:", issues.size());
            issues.forEach(issue -> 
                log.warn("  - {}: {}", issue.type(), issue.description()));
        }
    }
    
    private void checkDependencyConflicts(List<MigrationIssue> issues) {
        try {
            // 检查是否同时存在Tomcat和Jetty依赖
            boolean hasTomcat = ClassUtils.isPresent(
                "org.apache.catalina.startup.Tomcat", 
                context.getClassLoader()
            );
            
            boolean hasJetty = ClassUtils.isPresent(
                "org.eclipse.jetty.server.Server", 
                context.getClassLoader()
            );
            
            if (hasTomcat && hasJetty) {
                issues.add(new MigrationIssue(
                    "DEPENDENCY_CONFLICT",
                    "检测到同时存在Tomcat和Jetty依赖,请排除Tomcat"
                ));
            }
            
        } catch (Exception e) {
            issues.add(new MigrationIssue(
                "CHECK_ERROR", 
                "依赖检查失败: " + e.getMessage()
            ));
        }
    }
    
    private void checkTomcatSpecificConfigs(List<MigrationIssue> issues) {
        // 检查Tomcat特定配置
        Environment env = context.getEnvironment();
        
        Map<String, String> tomcatProperties = Map.of(
            "server.tomcat.max-threads", "server.jetty.threads.max",
            "server.tomcat.max-connections", "server.jetty.connection-idle-timeout",
            "server.tomcat.accesslog.enabled", "server.jetty.accesslog.enabled"
        );
        
        tomcatProperties.forEach((tomcatProp, jettyProp) -> {
            if (env.containsProperty(tomcatProp) && !env.containsProperty(jettyProp)) {
                issues.add(new MigrationIssue(
                    "CONFIG_MAPPING",
                    String.format("Tomcat配置'%s'需要映射到Jetty配置'%s'", 
                        tomcatProp, jettyProp)
                ));
            }
        });
    }
    
    private void checkCustomTomcatComponents(List<MigrationIssue> issues) {
        // 检查自定义Tomcat组件
        String[] tomcatBeanNames = context.getBeanNamesForType(
            org.apache.catalina.connector.Connector.class
        );
        
        if (tomcatBeanNames.length > 0) {
            issues.add(new MigrationIssue(
                "CUSTOM_COMPONENT",
                "发现自定义Tomcat组件: " + Arrays.toString(tomcatBeanNames)
            ));
        }
    }
    
    private void checkWebSocketConfig(List<MigrationIssue> issues) {
        // 检查WebSocket配置差异
        try {
            boolean hasTomcatWebSocket = ClassUtils.isPresent(
                "org.apache.tomcat.websocket.server.WsSci", 
                context.getClassLoader()
            );
            
            if (hasTomcatWebSocket) {
                issues.add(new MigrationIssue(
                    "WEBSOCKET_CONFIG",
                    "Tomcat WebSocket配置需要迁移到Jetty WebSocket"
                ));
            }
        } catch (Exception e) {
            // 忽略
        }
    }
    
    private void checkSslConfig(List<MigrationIssue> issues) {
        // SSL配置检查
        Environment env = context.getEnvironment();
        
        if (env.containsProperty("server.ssl.key-store-type")) {
            String keyStoreType = env.getProperty("server.ssl.key-store-type", "");
            
            // Jetty对PKCS12支持更好
            if ("JKS".equalsIgnoreCase(keyStoreType)) {
                issues.add(new MigrationIssue(
                    "SSL_CONFIG",
                    "建议将JKS格式密钥库转换为PKCS12格式以获得更好的Jetty兼容性"
                ));
            }
        }
    }
    
    record MigrationIssue(String type, String description) {}
}

六、企业级最佳实践

1. 多环境配置

yaml 复制代码
# application-dev.yml - 开发环境
server:
  jetty:
    threads:
      min: 5
      max: 50
    accesslog:
      enabled: true
    dump:
      enabled: true  # 开发环境启用dump

# application-test.yml - 测试环境
server:
  jetty:
    threads:
      min: 20
      max: 200
    accesslog:
      enabled: true
    dump:
      enabled: false

# application-prod.yml - 生产环境
server:
  jetty:
    threads:
      min: 50
      max: 500
    accesslog:
      enabled: true
      retain-days: 90
    dump:
      enabled: false
    gzip:
      enabled: true
      min-gzip-size: 1024

2. 安全配置

java 复制代码
@Configuration
@Slf4j
public class JettySecurityConfig {
    
    /**
     * Jetty安全配置
     */
    @Bean
    public HandlerWrapper securityHeadersHandler() {
        return new HandlerWrapper() {
            @Override
            public void handle(String target, Request baseRequest, 
                              HttpServletRequest request, HttpServletResponse response) 
                    throws IOException, ServletException {
                
                // 设置安全相关的HTTP头
                Map<String, String> securityHeaders = Map.of(
                    "X-Content-Type-Options", "nosniff",
                    "X-Frame-Options", "DENY",
                    "X-XSS-Protection", "1; mode=block",
                    "Strict-Transport-Security", "max-age=31536000; includeSubDomains",
                    "Content-Security-Policy", "default-src 'self'",
                    "Referrer-Policy", "strict-origin-when-cross-origin"
                );
                
                securityHeaders.forEach(response::setHeader);
                
                // 移除敏感头
                response.setHeader("Server", "Jetty");
                response.setHeader("X-Powered-By", null);
                
                // 继续处理链
                if (getHandler() != null) {
                    getHandler().handle(target, baseRequest, request, response);
                }
            }
        };
    }
    
    /**
     * 与Tomcat安全配置对比
     */
    @Bean
    @ConditionalOnProperty(name = "server.tomcat.security.enabled", havingValue = "true")
    public TomcatServletWebServerFactory secureTomcat() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        
        factory.addContextCustomizers(context -> {
            // Tomcat通过Context设置安全头
            context.addWelcomeFile("index.html");
            
            // 添加安全阀
            context.getPipeline().addValve(new RemoteIpValve());
        });
        
        factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"));
        
        return factory;
    }
}

七、性能测试对比

java 复制代码
@SpringBootTest
@ActiveProfiles("test")
@AutoConfigureMockMvc
@Slf4j
public class ServerPerformanceComparisonTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    /**
     * 对比Jetty和Tomcat的性能表现
     */
    @Test
    @Disabled("实际运行需要切换服务器")
    public void compareServerPerformance() throws Exception {
        int requestCount = 10000;
        int concurrentUsers = 100;
        
        log.info("🚀 开始性能对比测试");
        log.info("请求总数: {}, 并发用户: {}", requestCount, concurrentUsers);
        
        // 测试结果收集
        List<Long> responseTimes = new ArrayList<>();
        AtomicInteger successCount = new AtomicInteger();
        AtomicInteger errorCount = new AtomicInteger();
        
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(concurrentUsers);
        CountDownLatch latch = new CountDownLatch(requestCount);
        
        long startTime = System.currentTimeMillis();
        
        // 并发执行请求
        for (int i = 0; i < requestCount; i++) {
            executor.submit(() -> {
                try {
                    long requestStart = System.currentTimeMillis();
                    
                    mockMvc.perform(get("/api/health"))
                          .andExpect(status().isOk());
                    
                    long responseTime = System.currentTimeMillis() - requestStart;
                    responseTimes.add(responseTime);
                    successCount.incrementAndGet();
                    
                } catch (Exception e) {
                    errorCount.incrementAndGet();
                    log.error("请求失败", e);
                } finally {
                    latch.countDown();
                }
            });
        }
        
        // 等待所有请求完成
        latch.await();
        long totalTime = System.currentTimeMillis() - startTime;
        
        // 计算统计信息
        double qps = (double) successCount.get() / totalTime * 1000;
        double avgResponseTime = responseTimes.stream()
            .mapToLong(Long::longValue)
            .average()
            .orElse(0);
        
        double p95ResponseTime = calculatePercentile(responseTimes, 95);
        double p99ResponseTime = calculatePercentile(responseTimes, 99);
        
        // 输出结果
        log.info("📊 性能测试结果:");
        log.info("  总时间: {}ms", totalTime);
        log.info("  成功请求: {}", successCount.get());
        log.info("  失败请求: {}", errorCount.get());
        log.info("  QPS: {}/s", String.format("%.2f", qps));
        log.info("  平均响应时间: {}ms", String.format("%.2f", avgResponseTime));
        log.info("  P95响应时间: {}ms", String.format("%.2f", p95ResponseTime));
        log.info("  P99响应时间: {}ms", String.format("%.2f", p99ResponseTime));
        
        executor.shutdown();
    }
    
    private double calculatePercentile(List<Long> values, double percentile) {
        if (values.isEmpty()) return 0;
        
        List<Long> sorted = new ArrayList<>(values);
        Collections.sort(sorted);
        
        int index = (int) Math.ceil(percentile / 100.0 * sorted.size());
        return sorted.get(Math.min(index, sorted.size() - 1));
    }
    
    /**
     * 内存使用对比
     */
    @Test
    public void compareMemoryUsage() {
        Runtime runtime = Runtime.getRuntime();
        
        long totalMemory = runtime.totalMemory() / 1024 / 1024;
        long freeMemory = runtime.freeMemory() / 1024 / 1024;
        long usedMemory = totalMemory - freeMemory;
        long maxMemory = runtime.maxMemory() / 1024 / 1024;
        
        log.info("💾 内存使用情况:");
        log.info("  已用内存: {}MB", usedMemory);
        log.info("  总内存: {}MB", totalMemory);
        log.info("  最大内存: {}MB", maxMemory);
        log.info("  使用率: {}%", 
            String.format("%.1f", (double) usedMemory / totalMemory * 100));
    }
}

八、总结与选择建议

1. 选择决策树

选择Web服务器
应用类型?
传统Web应用
高并发/实时应用
微服务/云原生
需要XML配置?

选择Tomcat

都可以
追求极致性能?

选择Jetty

都可以
需要快速启动?

选择Jetty

都可以
决策: Tomcat
评估其他因素
团队熟悉度?
熟悉Tomcat
熟悉Jetty
特殊需求?
需要AJP
需要HTTP/3

2. 最终建议

选择Tomcat的场景

  1. 传统企业级Java EE应用
  2. 需要与Apache HTTP Server集成(AJP)
  3. 团队熟悉Tomcat,有丰富的运维经验
  4. 应用对Servlet规范的严格实现有要求

选择Jetty的场景

  1. 高并发、低延迟的实时应用
  2. 微服务架构,需要快速启动
  3. 资源受限的环境(容器、云函数)
  4. 需要先进的HTTP特性(HTTP/2、HTTP/3)
  5. 嵌入式应用(如开发工具、测试框架)

Spring Boot 3.2下的建议

  • 默认Tomcat:稳定可靠,社区支持好
  • 高并发场景:考虑切换到Jetty
  • 云原生:Jetty的轻量和快速启动更有优势
  • 开发体验:Jetty的热部署更好
相关推荐
贫民窟的勇敢爷们2 小时前
SpringBoot整合MyBatis-Plus极致实战,高效实现数据库CRUD与分页条件查询
数据库·spring boot·mybatis
阿丰资源2 小时前
基于SpringBoot的电影评论网站(含源码)
java·spring boot·后端
小码哥0682 小时前
2026版基于springboot的家政服务预约系统
java·spring boot·后端
杨运交2 小时前
[016][web模块]基于 MDC 的分布式追踪框架设计与实现
spring boot·后端
Albert Edison3 小时前
【RabbitMQ】SpringBoot 整合 RabbitMQ
spring boot·rabbitmq·java-rabbitmq
夕除3 小时前
spring boot 4
java·spring boot·后端
starsky762383 小时前
spring boot——前后端分离
java·spring boot·后端
i220818 Faiz Ul4 小时前
个人健康系统|健康管理|基于java+Android+微信小程序的个人健康系统设计与实现(源码+数据库+文档)
android·java·vue.js·spring boot·微信小程序·毕设·个人健康系统
河阿里19 小时前
SpringBoot:项目启动速度深度优化
java·spring boot·后端