在 Tomcat 的架构中,Acceptor
、Poller
和 Executor
是处理网络连接和请求的重要组件。
Acceptor
Acceptor 是 Tomcat 中负责接受新连接的组件。它的主要职责包括:
- 监听端口:Acceptor 在线程中监听一个特定的端口,等待客户端连接请求。
- 接受连接:当有新的连接请求到达时,Acceptor 负责接受该连接。
- 交给处理器:Acceptor 将接受到的连接交给连接处理器(如处理 HTTP 请求的线程池)进行具体的请求处理。
这种设计使得 Tomcat 能够高效地处理大量并发连接,因为接受连接的操作与实际处理请求的操作是分离的。
Poller
Poller 是 Tomcat 中负责监控已打开的连接的组件。它的主要职责包括:
- 监控事件:Poller 线程会监控已打开的连接,等待这些连接上发生的事件(如读取数据、写入数据等)。
- 事件分发:当连接上有事件发生时,Poller 会将这些事件分发给适当的处理器进行处理。
Poller 使用了非阻塞 I/O(如 Java NIO)的机制,这使得它能够高效地处理大量并发连接,而不会因为等待 I/O 操作而阻塞线程。
Executor
Executor 是 Tomcat 中用于管理线程池的组件。它的主要职责包括:
- 线程池管理:Executor 维护一个线程池,用于处理各种任务(如请求处理、连接处理等)。
- 任务调度:当有新的任务需要处理时,Executor 会从线程池中分配一个线程来执行该任务。
通过使用线程池,Tomcat 能够更好地管理系统资源,避免了频繁创建和销毁线程的开销,从而提高了性能。
工作流程
- 接受连接:Acceptor 线程监听端口,接受新的客户端连接。
- 注册事件:接受到连接后,Acceptor 将连接注册到 Poller 中,等待事件发生。
- 事件监控:Poller 线程监控连接上的事件,如数据可读或可写。
- 事件处理:当事件发生时,Poller 将事件分发给处理器(如请求处理器)进行处理。
- 请求处理:处理器从 Executor 的线程池中获取线程来处理请求。
Tomcat 关键源码:
Poller 核心作用就是 Nio的时间循环,处理读写事件
一个简单的NIO DEMO:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NioServer {
private Selector selector;
private ByteBuffer buffer = ByteBuffer.allocate(256);
public static void main(String[] args) throws IOException {
new NioServer().startServer();
}
public void startServer() throws IOException {
// 打开 Selector 和 ServerSocketChannel
selector = Selector.open();
ServerSocketChannel serverSocket = ServerSocketChannel.open();
// 绑定端口并配置为非阻塞模式
serverSocket.bind(new InetSocketAddress("localhost", 8080));
serverSocket.configureBlocking(false);
// 注册 ServerSocketChannel 到 Selector,监听连接事件
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started on port 8080");
while (true) {
// 阻塞直到有事件发生
selector.select();
// 获取所有发生事件的 SelectionKey
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
register(selector, serverSocket);
}
if (key.isReadable()) {
answerWithEcho(buffer, key);
}
iterator.remove();
}
}
}
private void register(Selector selector, ServerSocketChannel serverSocket) throws IOException {
// 接受客户端连接并配置为非阻塞模式
SocketChannel client = serverSocket.accept();
client.configureBlocking(false);
// 注册 SocketChannel 到 Selector,监听读事件
client.register(selector, SelectionKey.OP_READ);
System.out.println("Client connected: " + client.getRemoteAddress());
}
private void answerWithEcho(ByteBuffer buffer, SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
client.read(buffer);
buffer.flip();
client.write(buffer);
buffer.clear();
}
}
为什么 Tomcat Acceptor 单独抽取出来了
对比tomcat 源码和常规NIO写法,发现 tomcat 接受网络连接 accept()单独抽出来了,没有放到事件循环里,为什么要这么做呢?
Tomcat 中,Acceptor 是一个专门用于接受新连接的组件。将 Acceptor 独立出来有以下几个原因:
-
解耦和职责单一:
- Acceptor 的唯一职责是监听并接受新的客户端连接,这种职责单一的设计使得代码更加清晰、易于维护和扩展。
- 通过将接受连接与处理连接逻辑分离,Tomcat 可以更高效地管理连接的生命周期。
-
提高并发处理能力:
- Acceptor 独立运行在自己的线程中,可以持续不断地接受新的连接,而不会被具体的请求处理所阻塞。
- 这使得 Tomcat 能够在高并发场景下高效地处理大量的并发连接。
-
负载均衡和资源管理:
- 通过独立的 Acceptor 线程,Tomcat 可以更好地控制和分配系统资源。例如,可以限制 Acceptor 的数量以防止系统资源被耗尽。
- 独立的 Acceptor 还可以为不同的连接类型(如 HTTP 和 HTTPS)提供不同的处理机制和策略。
-
提高响应速度:
- 独立的 Acceptor 可以立即处理新的连接请求,而无需等待当前请求的处理完成,从而提高了服务器的响应速度。
-
容错和稳定性:
- 如果请求处理出现问题(如长时间阻塞或异常),Acceptor 线程不会受到影响,可以继续接受新的连接,从而提高了系统的稳定性和容错能力。
Tomcat Executor 线程池:
在 Tomcat 中,ThreadPoolExecutor
是一个关键组件,用于管理线程池,以便高效地处理并发请求。通过合理地利用ThreadPoolExecutor
,Tomcat 能够提高资源利用率,减少线程创建和销毁的开销,从而提升服务器的性能和响应速度。
-
线程池的基本概念:
- 线程复用:线程池中的线程可以被复用,避免了频繁创建和销毁线程的开销。
- 任务队列:任务队列用于存储等待执行的任务,当有可用线程时,会从队列中取出任务执行。
- 核心线程数:线程池会维护一定数量的核心线程,即使这些线程处于空闲状态,它们也不会被销毁。
- 最大线程数:线程池允许的最大线程数,如果任务量超过核心线程数时,会创建新的线程,但不会超过这个最大值。
- 存活时间:当线程池中线程数量超过核心线程数时,多余的线程在空闲时间超过设定的存活时间后会被终止。
-
ThreadPoolExecutor 的工作流程:
- 任务提交:当有新任务提交时,首先判断核心线程池是否已满,如果未满则创建新线程执行任务。
- 任务排队:如果核心线程池已满,则将任务加入任务队列。
- 线程扩展:当任务队列已满且线程池未达到最大线程数时,会创建新的线程执行任务。
- 任务拒绝:如果任务队列已满且线程池已达到最大线程数,则会根据拒绝策略处理新提交的任务。
标签:tomcat,Tomcat,处理,线程,Poller,连接,Acceptor From: https://blog.csdn.net/zfj321/article/details/144952392