你的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的场景:
- 传统企业级Java EE应用
- 需要与Apache HTTP Server集成(AJP)
- 团队熟悉Tomcat,有丰富的运维经验
- 应用对Servlet规范的严格实现有要求
选择Jetty的场景:
- 高并发、低延迟的实时应用
- 微服务架构,需要快速启动
- 资源受限的环境(容器、云函数)
- 需要先进的HTTP特性(HTTP/2、HTTP/3)
- 嵌入式应用(如开发工具、测试框架)
Spring Boot 3.2下的建议:
- 默认Tomcat:稳定可靠,社区支持好
- 高并发场景:考虑切换到Jetty
- 云原生:Jetty的轻量和快速启动更有优势
- 开发体验:Jetty的热部署更好