你的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容器

核心差异表

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

2. 性能特性对比

// 性能测试对比工具
@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默认):

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

切换到Jetty

<!-- 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 验证切换结果
@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配置
# 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 编程式配置
@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. 高级配置:线程池优化

@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. 连接器配置优化

# 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配置

@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. 监控与指标

@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架构

@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支持

@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. 依赖和配置迁移

# 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. 迁移检查工具

@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. 多环境配置

# 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. 安全配置

@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;
    }
}

七、性能测试对比

@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的热部署更好
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐