如何启动
在 SpringBoot 启动流程中,将调用到以下代码
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 获取到 ServletWebServerFactory 的实现类:TomcatServletWebServerFactory
// ServletWebServerFactoryConfiguration,由自动化配置类 ServletWebServerFactoryConfiguration 加载
ServletWebServerFactory factory = getWebServerFactory();
// 最终创建出来的 webServer 为 TomcatWebServer。具体细节见下文
this.webServer = factory.getWebServer(getSelfInitializer());
// 注册优雅关闭的 hook,本质上是 SpringBoot 提供的钩子接口 SmartLifecycle 的实现类 在 IOC 容器刷新、关闭时进行 start、stop 方法的回调
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
通过 TomcatServletWebServerFactory 工厂创建 TomcatWebServer
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
// 创建临时目录
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 创建 Connector,该组件为核心组件,通过组合 ProtocolHandler 接口实现类实现:接收网络请求,读写数据流。默认情况下使用的是 Http11NioProtocol
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
// 将 Connector 添加到 Service 组件中,该调用将触发 StandardServer 的实例化,伴随着触发 StandardService 的实例化。
tomcat.getService().addConnector(connector);
// 定制化 Connector,例如最大、最小空闲数等
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
// 创建 TomcatWebServer 实例,并初始化、启动其包含的子组件。关于子组件之间的关系,详见下文。
return getTomcatWebServer(tomcat);
}
在上面的代码中,将会调用 Tomcat.start 方法,对 Tomcat 及其子组件进行初始化和启动操作,具体流程如下:
WebServerGracefulShutdownLifecycle 作为 SmartLifecycle 接口实现类,在 SpringBoot 应用启动过程中将会被回调其 start 方法,具体作用是为了启动 Connector 进行端口监听,以及启动接入连接、处理网络 IO 数据的线程。
通过上面两个流程:
- 初始化 Tomcat、TomcatWebServer
- 回调 WebServerGracefulShutdownLifecycle
我们可以看出 TomcatWebServer,作为最上层的门面。封装了 Tomcat;
而 Tomcat 又是更深一层的包装,对 StandardServer、StandardService、Connector、ProtocolHandler(接口,交互协议和IO模式的抽象,具体实现类有 Http11NioProtocol、Http11Nio2Protocol、Http11AprProtocol、AjpAprProtocol等等,其中 Http11NioProtocol 是默认使用的实现)。
StandardServer 与 StandardService 是父子组件的关系,StandardServer 持有 StandardService 的实例。而且可以持有多个 Service。(在 SpringBoot 内嵌tomcat的模式下只会有一个)
StandardService 和 Connector 同样也是父子组件的关系,StandardService 持有 Connector 的实例。而且可以持有多个 Connector。(在 SpringBoot 内嵌 tomcat 的模式下只有一个)
Connector 和 ProtocolHandler 也是父子组件的关系,Connector 和 ProtocolHandler 是一对一的。在 tomcat 的设计思想上,ProtocolHandler 作为协议交互的抽象,对交互协议和 IO 模式进行抽象,暴露出通用的接口。
ProtocolHandler 通过组合 Endpoint 接口实现类的方式拥有了处理请求的能力。Endpoint 的实现类有 AprEndpoint、NioEndpoint、Nio2Endpoint。
默认情况下,使用的 ProtocolHandler 实现类为 Http11NioProtocol, 其内部持有一个 NioEndpoint 实例。从命名上看,Http11NioProtocol 是处理 HTTP1.1 协议的连接器,并且以 NIO 模式进行 IO 操作。组合 Http11Processor 处理 Http1.1 协议,组合 NioEndpoint 处理请求和数据读写。
**
以上就是应用启动时,tomcat 初始化和启动的整体流程,这里先留下三个悬念:
- BlockPoller
- Poller
- Acceptor
这三者是干什么用的?
如何接收和处理请求
回顾一下 NioEndpoint 启动的过程。
// org.apache.tomcat.util.net.AbstractEndpoint#start
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
bindWithCleanup();
bindState = BindState.BOUND_ON_START;
}
startInternal();
}
// org.apache.tomcat.util.net.AbstractEndpoint#bindWithCleanup
private void bindWithCleanup() throws Exception {
try {
bind();
} catch (Throwable t) {
// Ensure open sockets etc. are cleaned up if something goes
// wrong during bind
ExceptionUtils.handleThrowable(t);
unbind();
throw t;
}
}
// org.apache.tomcat.util.net.NioEndpoint#bind
@Override
public void bind() throws Exception {
initServerSocket();
setStopLatch(new CountDownLatch(1));
// Initialize SSL if needed
initialiseSsl();
// NioSelectorPool
selectorPool.open(getName());
}
// org.apache.tomcat.util.net.NioEndpoint#initServerSocket
protected void initServerSocket() throws Exception {
if (!getUseInheritedChannel()) {
// java nio 网络编程的相关接口
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
serverSock.socket().bind(addr,getAcceptCount());
} else {
// Retrieve the channel provided by the OS
Channel ic = System.inheritedChannel();
if (ic instanceof ServerSocketChannel) {
serverSock = (ServerSocketChannel) ic;
}
if (serverSock == null) {
throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
}
}
serverSock.configureBlocking(true); //mimic APR behavior
}
// org.apache.tomcat.util.net.NioSelectorPool#open
public void open(String name) throws IOException {
enabled = true;
getSharedSelector();
if (shared) {
// 启动 BlockPoller 线程
blockingSelector = new NioBlockingSelector();
blockingSelector.open(name, getSharedSelector());
}
}
// org.apache.tomcat.util.net.NioEndpoint#startInternal
@Override
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
// 三个对象池缓存
if (socketProperties.getProcessorCache() != 0) {
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
}
if (socketProperties.getEventCache() != 0) {
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
}
if (socketProperties.getBufferPool() != 0) {
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
}
// 创建用于 ? 的线程池
// Create worker collection
if (getExecutor() == null) {
createExecutor();
}
// 限制最大连接数
initializeConnectionLatch();
// 启动线程,处理 PollerEvent 事件和 IO 事件
// Start poller thread
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-ClientPoller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
// accept 新连接,通过发送 PollerEvent 的方式进行 IO 数据读写。
startAcceptorThread();
}
}
从以上代码可以看出,NioEndpoint 启动时,启动了一个接受连接的 Acceptor 线程、一个处理事件的 Poller 线程。以下是 tomcat 接收请求的流程:
一个请求被封装为 Runnable 任务后,丢到线程池之后的执行流程如下:
其中 CoyoteAdapter 是适配器模式的实现,作为连接 Connector 和 Servlet 容器(即 StandardEngine)的桥梁,解耦和两者之间的耦合。
了解了处理请求的整个过程之后,来看下前面留下的三个悬念:
- BlockPoller
- Poller
- Acceptor
这三者是干什么用的?
目前我们已经知道 Acceptor 是接收新连接的线程,而 Poller 是负责处理网络请求的读写操作的线程(即处理 Selector 事件以及 PollerEvent 事件)。
至于 BlockPoller,要理解它的作用需要看一下 IO 写出数据时的流程。写出数据时,最终会调用到以下代码,主要是 30 - 38 行
// org.apache.tomcat.util.net.NioBlockingSelector#write
public int write(ByteBuffer buf, NioChannel socket, long writeTimeout)
throws IOException {
SelectionKey key = socket.getIOChannel().keyFor(socket.getSocketWrapper().getPoller().getSelector());
if (key == null) {
throw new IOException(sm.getString("nioBlockingSelector.keyNotRegistered"));
}
KeyReference reference = keyReferenceStack.pop();
if (reference == null) {
reference = new KeyReference();
}
NioSocketWrapper att = (NioSocketWrapper) key.attachment();
int written = 0;
boolean timedout = false;
int keycount = 1; //assume we can write
long time = System.currentTimeMillis(); //start the timeout timer
try {
while (!timedout && buf.hasRemaining()) {
if (keycount > 0) { //only write if we were registered for a write
int cnt = socket.write(buf); //write the data
if (cnt == -1) {
throw new EOFException();
}
written += cnt;
if (cnt > 0) {
time = System.currentTimeMillis(); //reset our timeout timer
continue; //we successfully wrote, try again without a selector
}
}
// 走到这里,说明 cnt < 0,说明网络抖动或者输出缓冲写满了,无法继续堆外输出数据,需要先挂起线程
try {
if (att.getWriteLatch() == null || att.getWriteLatch().getCount() == 0) {
att.startWriteLatch(1);
}
// 添加到 BlockPoller的队列中,为了在 IO 可写时唤醒当前线程
poller.add(att, SelectionKey.OP_WRITE, reference);
// 线程等待 selector 可写事件
att.awaitWriteLatch(AbstractEndpoint.toTimeout(writeTimeout), TimeUnit.MILLISECONDS);
} catch (InterruptedException ignore) {
// Ignore
}
if (att.getWriteLatch() != null && att.getWriteLatch().getCount() > 0) {
//we got interrupted, but we haven't received notification from the poller.
keycount = 0;
} else {
//latch countdown has happened
keycount = 1;
att.resetWriteLatch();
}
if (writeTimeout > 0 && (keycount == 0)) {
timedout = (System.currentTimeMillis() - time) >= writeTimeout;
}
}
if (timedout) {
throw new SocketTimeoutException();
}
} finally {
poller.remove(att, SelectionKey.OP_WRITE);
if (timedout && reference.key != null) {
poller.cancelKey(reference.key);
}
reference.key = null;
keyReferenceStack.push(reference);
}
return written;
}
BlockPoller 线程通过同样会轮训 Selector 的 IO 事件,并且在检测到 IO 事件后,唤醒在 write 调用时等待的线程(通过 CountDownLatch 实现), 主要逻辑在 46 到 62 行。
@Override
public void run() {
while (run) {
try {
events();
int keyCount = 0;
try {
int i = wakeupCounter.get();
if (i > 0) {
keyCount = selector.selectNow();
} else {
wakeupCounter.set(-1);
keyCount = selector.select(1000);
}
wakeupCounter.set(0);
if (!run) {
break;
}
} catch (NullPointerException x) {
// sun bug 5076772 on windows JDK 1.5
if (selector == null) {
throw x;
}
if (log.isDebugEnabled()) {
log.debug("Possibly encountered sun bug 5076772 on windows JDK 1.5", x);
}
continue;
} catch (CancelledKeyException x) {
// sun bug 5076772 on windows JDK 1.5
if (log.isDebugEnabled()) {
log.debug("Possibly encountered sun bug 5076772 on windows JDK 1.5", x);
}
continue;
} catch (Throwable x) {
ExceptionUtils.handleThrowable(x);
log.error(sm.getString("nioBlockingSelector.selectError"), x);
continue;
}
Iterator<SelectionKey> iterator = keyCount > 0
? selector.selectedKeys().iterator()
: null;
// Walk through the collection of ready keys and dispatch
// any active event.
while (run && iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
try {
iterator.remove();
// 相当于忽略 Selector 事件,让 countDownlatch 等待的线程来处理该事件
sk.interestOps(sk.interestOps() & (~sk.readyOps()));
if (sk.isReadable()) {
// 唤醒线程
countDown(socketWrapper.getReadLatch());
}
if (sk.isWritable()) {
// 唤醒线程
countDown(socketWrapper.getWriteLatch());
}
} catch (CancelledKeyException ckx) {
sk.cancel();
countDown(socketWrapper.getReadLatch());
countDown(socketWrapper.getWriteLatch());
}
}
} catch (Throwable t) {
log.error(sm.getString("nioBlockingSelector.processingError"), t);
}
}
events.clear();
// If using a shared selector, the NioSelectorPool will also try and
// close the selector. Try and avoid the ClosedSelectorException
// although because multiple threads are involved there is always
// the possibility of an Exception here.
if (selector.isOpen()) {
try {
// Cancels all remaining keys
selector.selectNow();
} catch (Exception ignore) {
if (log.isDebugEnabled())
log.debug("", ignore);
}
}
try {
selector.close();
} catch (Exception ignore) {
if (log.isDebugEnabled())
log.debug("", ignore);
}
}
总结
通过上面的分析,我们可以看到 Tomcat 的整体架构,其中设计到的类主要为以下几个:
并且处理请求的线程模型如下