对于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 不能调用initif (!state.equals(LifecycleState.NEW)) {invalidTransition(Lifecycle.BEFORE_INIT_EVENT);}try {// 初始化之前 状态变更为initsetStateInternal(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// Nio2Endpointbind();// 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) {// 启动所有的 serviceservice.start();
}synchronized (engine) {// 启动 Engine 子容器engine.start();
}// 可以看到这里通过线程池进行异步处理
for (Container child : children) {// 这就是 Engine init 过程中构建好的线程池// 这个线程池是在实例化 Engine 时给 Host 用的// 处理逻辑在 StandardHost 中的 startInternalresults.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 来处理。具体的流程在流程图中。