对于web后端开发工程师来说,tomcat作为一个应用服务器框架本质上就是一个HTTP服务+Servlet容器。研究过spring、spring mvc源码的同学应该了解,spring mvc其实就是基于Servlet规范实现的请求的转发路由、转发处理。而Spring和SpringMVC就是通过web.xml文件中的事件监听器ContextListener加载Spring容器,然后在加载SpringMVC子容器。
所以带着几个问题,去学习tomcat的原理,本篇先聊聊基础架构,以及核心的启动流程。
- 基本架构如何,以及各个组件如何功能
- 启动的核心流程
- 处理请求的核心流程
- tomcat的是如何打破双亲委派模型,进行的类加载
- tomcat的线程模型
- tomcat的性能调优
tomcat系统架构
对于tomcat来说,比较重要的就是server.xml文件
1 <Server> //顶层组件,可以包括多个Service
2 <Service> //顶层组件,可包含一个Engine,多个连接器
3 <Connector/> //连接器组件,代表通信接口
4 <Engine> //容器组件,一个Engine组件处理Service中的所有请求,包含多个Host
5 <Host> //容器组件,处理特定的Host下客户请求,可包含多个Context
6 <Context/> //容器组件,为特定的Web应用处理所有的客户请求
7 </Host>
8 </Engine>
9 </Service>
10 </Server>
Connector 连接器:对外交流
Container 容器:内部处理
Server:负责和管理启动多个service,加痛8005端口的shutdown命令。关闭整个容器
启动流程
启动类就是BootStrap的main方法。
init
初始化反射生成一个Catalina对象。这里有类加载器的操作,先不介绍,后面在进行单独讲解。
daemon.load(args);
反射调用catalina.load()方法
digester.parse(inputSource); // 解析server.xml文件 构建对象
getServer().init(); // 初始化 可以看到这里是StandardServer
public final class StandardServer extends LifecycleMBeanBase implements Server
// 因为StandardServer继承了LifeCycleMBeanBase 先调用父类的init()
public final synchronized void init() throws LifecycleException {
// 不是new 不能调用init
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
// 初始化之前 状态变更为init
setStateInternal(LifecycleState.INITIALIZING, null, false);
// 模版方法 拓展使用
initInternal();
// 初始化完成
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
// 初始化异常 修改成 公共逻辑
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
for (Service service : services) {
// Service组件初始化
service.init();
}
这里只有一个对象,所以对StandardService进行初始化,在初始化Service的时候,发现需要先初始化engine
if (engine != null) {
// 1.Engine组件, 即servlet容器 初始化
// 创建了一个线程池用于后续start流程中的 Host 的启动
engine.init();
}
// Initialize our defined Connectors
synchronized (connectorsLock) { // 加锁操作
for (Connector connector : connectors) {
// 2. Connector组件 初始化
// endpoint 绑定端口
connector.init();
}
}
// TODO 重点关注 Endpoint 的端口绑定与 NIO的监听
protocolHandler.init();
// Endpoint 组件的初始化
endpoint.init();
// 三种实现, 默认 NioEndpoint
// BioEndpoint
// NioEndpoint
// Nio2Endpoint
bind();
// NIO之 获取通道 SocketChannel
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
// NIO之 绑定端口( IP:PORT )
serverSock.socket().bind(addr,getAcceptCount());
总结下,其实初始化的过程就是把一些基础的东西进行加载。主要是绑定8090端口。
start
这里执行start方法,会去反射调用catalina.start()方法
// TODO 3.真正启动
daemon.start();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
// 执行启动
getServer().start();
// 循环遍历service
for (Service service : services) {
// 启动所有的 service
service.start();
}
synchronized (engine) {
// 启动 Engine 子容器
engine.start();
}
// 可以看到这里通过线程池进行异步处理
for (Container child : children) {
// 这就是 Engine init 过程中构建好的线程池
// 这个线程池是在实例化 Engine 时给 Host 用的
// 处理逻辑在 StandardHost 中的 startInternal
results.add(startStopExecutor.submit(new StartChild(child)));
}
open();
// 启动 Connector 组件
connector.start();
protocolHandler.start();
endpoint.start();
// 创建线程池
public void createExecutor() {
internalExecutor = true;
TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);
}
// 等待
public void await() {
getServer().await();
}
小结
其实就是三个流程,初始化、解析文件、启动。
而最终的处理请求由acceptor\poller\worker 来处理。具体的流程在流程图中。