今天我们深入讨论 Tomcat 和 Jetty 作为 Java 生态中两大高性能、高并发 Web 容器的实现之道。高性能程序的核心目标是高效利用系统资源:CPU、内存、网络和磁盘,同时在短时间内处理大量请求。这就涉及到两个关键指标:响应时间 和 每秒事务处理量(TPS)。
在实际工作中,Tomcat 和 Jetty 通过以下技术来实现高性能与高并发:
- 高效的 I/O 和线程模型。
- 减少 系统调用。
- 池化 资源的复用。
- 使用 零拷贝 提升网络传输效率。
- 应用 高效的并发编程 技术。
接下来,我们从源码角度逐一分析 Tomcat 和 Jetty 的高性能设计,并配以详尽注释和讲解。
一、I/O 和线程模型
1.1 Tomcat 的 BIO、NIO 和 APR 模式
Tomcat 提供三种 I/O 模型:
- BIO(Blocking I/O):每个请求一个线程,适合低并发场景。
- NIO(Non-Blocking I/O):使用 Java NIO,适合中高并发场景。
- APR(Apache Portable Runtime):基于 Native 的非阻塞 I/O 模型,适合超高并发场景。
核心源码:NIO 的实现(Tomcat 8.5)
在 org.apache.tomcat.util.net.NioEndpoint 中定义了 NIO 模型的主流程:
@Override
public boolean processSocket(SocketWrapperBase<NioChannel> socketWrapper,
SocketEvent event, boolean dispatch) {
NioChannel socket = socketWrapper.getSocket();
try {
if (event == SocketEvent.OPEN_READ) {
int nRead = socket.read(buffer);
if (nRead > 0) {
// 将数据提交到处理线程池
processRead(buffer, socket);
} else if (nRead == -1) {
// 客户端断开连接
closeSocket(socket);
}
} else if (event == SocketEvent.OPEN_WRITE) {
int nWrite = socket.write(buffer);
if (nWrite > 0) {
// 继续处理写事件
processWrite(buffer, socket);
}
}
} catch (IOException e) {
log.error("NIO I/O Error: " + e.getMessage());
}
return true;
}
代码解析:
- 读取数据:socket.read(buffer) 通过非阻塞方式从客户端读取数据。
- 事件驱动:根据事件类型(OPEN_READ 或 OPEN_WRITE)决定后续逻辑。
- 线程池提交:数据读取后会交由线程池处理,避免阻塞主线程。
1.2 Jetty 的 Selector 和线程池模型
Jetty 使用 Selector + Reactor 模式处理非阻塞 I/O 请求,其核心逻辑在 org.eclipse.jetty.io.SelectorManager 中。
核心源码:Jetty Selector 的实现
protected void runSelector() {
while (isRunning()) {
try {
int selected = selector.select(); // 阻塞等待事件发生
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
if (key.isReadable()) {
handleRead(key);
} else if (key.isWritable()) {
handleWrite(key);
}
}
keys.clear();
} catch (IOException e) {
log.warn("Selector Error: " + e.getMessage());
}
}
}
代码解析:
- Selector:监听多个通道,避免为每个请求创建一个线程。
- 事件驱动:基于通道的可读/可写事件执行特定逻辑。
- 优化的线程池:Jetty 的线程池实现动态调整线程数量,避免线程上下文切换带来的开销。
二、减少系统调用
系统调用(如文件读写、网络传输)是高性能系统中的常见瓶颈。Tomcat 和 Jetty 通过减少上下文切换和优化内核交互,大幅提升性能。
2.1 Tomcat 的 Native APR 优化
Tomcat 的 APR 模型直接使用 Apache Portable Runtime 提供的 C 语言库,与操作系统的网络栈高效交互。
核心源码:APR 的文件描述符复用
int rv = apr_socket_recv(sock, buffer, &len);
if (rv != APR_SUCCESS) {
// 错误处理
return APR_ERROR;
}
- APR 直接操作文件描述符,绕过 Java 层的中间调用,减少开销。
- 使用操作系统支持的事件通知机制(如 epoll/kqueue)高效管理大量连接。
2.2 Jetty 的 Direct ByteBuffer 优化
Jetty 使用 Direct ByteBuffer 将数据直接写入操作系统内核。
核心源码:Direct Buffer 使用
ByteBuffer directBuffer = ByteBuffer.allocateDirect(4096); // 直接分配内存
socketChannel.write(directBuffer); // 避免内核与用户态的内存拷贝
- 避免复制:直接内存不经过 JVM 堆,避免堆到本地内存的中间拷贝。
- 更快的 I/O:通过减少上下文切换,提高 I/O 速度。
三、池化技术的应用
3.1 线程池的实现
Tomcat 和 Jetty 都使用线程池管理线程资源,避免频繁创建销毁线程。
核心源码:Tomcat 线程池的实现
public class TaskThreadFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "TaskThread-" + threadNumber.getAndIncrement());
t.setDaemon(true);
return t;
}
}
优点:
- 线程复用:避免创建/销毁线程的开销。
- 动态调整:根据负载动态增加或减少线程。
3.2 Jetty 的连接池优化
Jetty 内置的连接池复用网络连接,提高了长连接场景的效率。
核心源码:Jetty 连接池实现
public Connection acquire() {
Connection connection = pool.poll();
if (connection == null) {
connection = new Connection();
}
return connection;
}
- 复用连接:通过连接池避免频繁建立和关闭网络连接。
- 负载均衡:支持多连接的负载分发。
四、零拷贝技术
零拷贝(Zero-Copy)是一种通过直接将文件数据从磁盘发送到网络而不经过用户空间的技术。
4.1 Tomcat 的零拷贝实现
核心源码:使用 sendfile 系统调用
sendfile(socket, fileDescriptor, offset, length);
- 内核态传输:数据直接从磁盘传输到网络,无需进入用户态。
- 减少 CPU 占用:提升文件传输效率。
4.2 Jetty 的零拷贝优化
Jetty 使用 MappedByteBuffer 和 FileChannel 实现高效的文件传输。
核心源码:FileChannel 的使用
FileChannel fileChannel = FileChannel.open(filePath, StandardOpenOption.READ);
fileChannel.transferTo(0, fileChannel.size(), socketChannel);
- 文件直接传输:通过 transferTo 方法将数据直接写入网络通道。
- 大文件支持:特别适合大文件的传输场景。
五、高效的并发编程
5.1 异步处理
Tomcat 和 Jetty 都支持 Servlet 3.0 的异步处理机制。
核心源码:Tomcat 异步处理
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
AsyncContext asyncContext = req.startAsync();
asyncContext.start(() -> {
// 异步执行逻辑
processRequest();
asyncContext.complete();
});
}
5.2 Jetty 的异步 API
Jetty 提供内置的异步 API,减少阻塞操作。
核心源码:Jetty 异步执行
CompletableFuture.runAsync(() -> {
// 异步逻辑
processRequest();
}).thenAccept(response -> sendResponse(response));
总结
通过分析 Tomcat 和 Jetty 的高性能设计,我们可以得出以下优化原则:
- 高效 I/O 模型:选择合适的 BIO/NIO/异步 I/O 模型。
- 减少系统调用:利用 APR 和 Direct ByteBuffer 技术。
- 资源池化:线程池和连接池优化资源利用。
- 零拷贝技术:提升大文件传输效率。
- 高效并发编程:采用异步处理和非阻塞操作。