共计 7993 个字符,预计需要花费 20 分钟才能阅读完成。
以后端发动一次申请,我想理解 tomcat 是如何解决的。这里写一些文章做一些简短的记录,不便后续温习。
Connector 的创立:当实例化一个 Connector,结构器函数会通过反射的形式创立一个 ProtocolHandler。这里的 protocolHandlerClassName 实际上是:”org.apache.coyote.http11.Http11NioProtocol”;
public Connector(String protocol) {setProtocol(protocol);
// Instantiate protocol handler
ProtocolHandler p = null;
try {Class<?> clazz = Class.forName(protocolHandlerClassName);
p = (ProtocolHandler) clazz.getConstructor().newInstance();
} catch (Exception e) {log.error(sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"), e);
} finally {this.protocolHandler = p;}
...
}
为什么要在创立 Connector 的时候绑定一个 ProtocolHandler 呢?其实情理很简略,当客户端和服务器发送网络申请时,必须约定协定,tomcat 经常作为 web 端服务器,因而默认提供解决 http 协定的 ProtocolHandler。
而在创立 Http11NioProtocol 过程中,又创立了上面的内容:
public Http11NioProtocol() {
// 创立一个 Nio 模型的端点
super(new NioEndpoint());
}
public AbstractHttp11Protocol(AbstractEndpoint<S,?> endpoint) {super(endpoint);
setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
// 创立一个 ConnectionHandler 来解决链接
ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
setHandler(cHandler);
getEndpoint().setHandler(cHandler);
}
Connector 的初始化:
Tomcat 外围的组件都实现了 Lifecycle 接口,被治理的接口,大略都是经验
init()->start()->stop() 的生命周期。
tomcat 为了对立治理 Lifecycle 的组件,提供了抽象类 LifecycleBase(实现了 Lifecycle 接口办法),每一个继承 LifecycleBase 的组件都会有一个状态,它的默认值是:
private volatile LifecycleState state = LifecycleState.NEW;
上面是 LifecycleBase 提供 init()的模板办法:
@Override
public final synchronized void init() throws LifecycleException {
// 对于任何一个组件来说,肯定是在新生状态才可能被初始化,如果不是则抛出异样
if (!state.equals(LifecycleState.NEW)) {invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
// init 前的状态
setStateInternal(LifecycleState.INITIALIZING, null, false);
// 真正的初始化办法,每个子类必须实现它
initInternal();
// init 后的状态
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
这个办法有两个要学习的中央:
第一在办法上应用了 synchronized+ 状态判断,比较简单的形式就保障一个了组件,只能初始化。而状态的应用也是很有用的,当咱们有一堆组件,组合成某种性能时,只有当所有组件都处于某种失常的状态时,才可能提供服务的。
第二是利用了抽象类,对立标准了初始化的流程。这里也能够看出接口和抽象类的区别,接口更像是定义了一些系列协定。而抽象类表白的是,你想要实现某种性能,你的规范流程是什么?
因而 Connector 继承了 LifecycleBase,所以要看 Connector 在初始化,都干了什么事,就找到对应的 initInternal():
@Override
protected void initInternal() throws LifecycleException {super.initInternal();
// 指定了适配器
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
....
try {
// Http11NioProtocol init
protocolHandler.init();} catch (Exception e) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
上面看下 protocolHandler.init(),它实际上要调用 AbstractProtocol 的 init(), 最初调用 NioEndpoint 的 init()-> bindWithCleanup()->bind():
public void init() throws Exception {if (bindOnInit) {
// 初始化服务器
bindWithCleanup();
bindState = BindState.BOUND_ON_INIT;
}
....
}
@Override
public void bind() throws Exception {
// 重点!
initServerSocket();
setStopLatch(new CountDownLatch(1));
// Initialize SSL if needed
initialiseSsl();}
protected void initServerSocket() throws Exception {if (getUseInheritedChannel()) {
// 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"));
}
} else {serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
// 获取地址,绑定服务器端口
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
serverSock.socket().bind(addr,getAcceptCount());
}
// 服务主线程,次要负责链接的申请,为了管制申请的大小
// 所以这里采纳了阻塞模式
serverSock.configureBlocking(true); //mimic(模拟) APR behavior
}
其中 getAcceptCount()默认值为:100
在这里咱们能够看到,咱们平时配置的端口时如何失效的,就在 addr 中
tomcat 中的 NIO 并不全部都是 NIO, 对于每个客户端的申请,任然应用的时阻塞模式。
到了这里 Connector 实现了它的初始化工作,留神并没有启动!
Connector 开始运行:这里依照 Lifecycle 的思维,间接看 Connector 的 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
// endpoint 获取申请后,会交给 worker 去执行,主线程仅仅是获取申请
// 另外这里采纳了非阻塞 io, 期待链接是阻塞式的,解决其余读写事件是用了非阻塞 IO
if (getExecutor() == null) {createExecutor();
}
// 设置最大的链接数:8192。这里须要留神,这里是如何工作的,// 因为应用了 Latch,因而每来一个链接,maxConnection 就缩小 1. 直到为 0
// 当为 0 的时候,不能再承受多的链接申请了
initializeConnectionLatch();
// Start poller thread
poller = new Poller();// 关上选择器,Poller 其实是一个 Runnable
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();// poller 开始工作。Poller 那么这里去看一下 run()
// 开启 Acceptor Thread 专门负责接管 socket 链接,留神它并不进行 socket 解决
startAcceptorThread();}
}
wokers 和 Poller 在前面说
接着咱们看下 startAcceptorThread()
protected void startAcceptorThread() {acceptor = new Acceptor<>(this);
String threadName = getName() + "-Acceptor";
acceptor.setThreadName(threadName);
Thread t = new Thread(acceptor, threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();}
Poller 和 Acceptor 都是线程,因而想要明确它们工作内容,之间看对应的 run();
首先是 Acceptor:
@Override
public void run() {
int errorDelay = 0;
long pauseStart = 0;
try {
// Loop until we receive a shutdown command
while (!stopCalled) {
// Loop if endpoint is paused.
// There are two likely scenarios here.
// The first scenario is that Tomcat is shutting down. In this
// case - and particularly for the unit tests - we want to exit
// this loop as quickly as possible. The second scenario is a
// genuine pause of the connector. In this case we want to avoid
// excessive CPU usage.
// Therefore, we start with a tight loop but if there isn't a
// rapid transition to stop then sleeps are introduced.
// < 1ms - tight loop
// 1ms to 10ms - 1ms sleep
// > 10ms - 10ms sleep
while (endpoint.isPaused() && !stopCalled) {if (state != AcceptorState.PAUSED) {pauseStart = System.nanoTime();
// Entered pause state
state = AcceptorState.PAUSED;
}
if ((System.nanoTime() - pauseStart) > 1_000_000) {
// Paused for more than 1ms
try {if ((System.nanoTime() - pauseStart) > 10_000_000) {Thread.sleep(10);
} else {Thread.sleep(1);
}
} catch (InterruptedException e) {// Ignore}
}
}
if (stopCalled) {break;}
state = AcceptorState.RUNNING;
try {
// if we have reached max connections, wait
// 限度了最大的申请数。endpoint.countUpOrAwaitConnection();
// Endpoint might have been paused while waiting for latch
// If that is the case, don't accept new connections
if (endpoint.isPaused()) {continue;}
U socket = null;
try {
// Accept the next incoming connection from the server socket
socket = endpoint.serverSocketAccept();} catch (Exception ioe) {
// We didn't get a socket
endpoint.countDownConnection();
if (endpoint.isRunning()) {
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
} else {break;}
}
// Successful accept, reset the error delay
errorDelay = 0;
// Configure the socket
if (!stopCalled && !endpoint.isPaused()) {// setSocketOptions() will hand the socket off to
// an appropriate processor if successful
// 解决的 socket
if (!endpoint.processSocket(socket)) {endpoint.closeSocket(socket);
}
} else {endpoint.destroySocket(socket);
}
} catch (Throwable t) {ExceptionUtils.handleThrowable(t);
String msg = sm.getString("endpoint.accept.fail");
// APR specific.
// Could push this down but not sure it is worth the trouble.
if (t instanceof org.apache.tomcat.jni.Error) {org.apache.tomcat.jni.Error e = (org.apache.tomcat.jni.Error) t;
if (e.getError() == 233) {
// Not an error on HP-UX so log as a warning
// so it can be filtered out on that platform
// See bug 50273
log.warn(msg, t);
} else {log.error(msg, t);
}
} else {log.error(msg, t);
}
}
}
} finally {stopLatch.countDown();
}
state = AcceptorState.ENDED;
}
Acceptor 作为一个线程始终 while 循环,直到 stop。通过代码也大略看得出
,它的次要工作就是负责承受申请。
在申请到来后,Acceptor 会分派给 Poller 去解决。那么有一个问题是,如果不限度申请量大小,服务器会解体,那么接着就会产生一个疑难,既然 tomcat 有并发限度,为什么咱们还要对接口做性能调优?
在这个办法中,有一行很重要的代码 endpoint.processSocket(socket)。最后源代码叫 endpoint.setSocketOptions(socket),我为了浏览不便,调整成 processSocket,前面整体了解了下 processSocket 的确不太好,因为 Acceptor 仅仅负责承受申请,它并不解决申请。
因为当初仅仅是 start,并没有申请到来,因而线程被阻塞在 socket = endpoint.serverSocketAccept(); 这个办法实际上是:
protected SocketChannel serverSocketAccept() throws Exception {
// 因为是阻塞模式,因而没有新的申请到来时,线程被阻塞在这里了
SocketChannel result = serverSock.accept();
return result;
}
好了,到了这里,Connector 曾经启动好了,能够承受客户端的申请了
Poller 呢?因为它和链接的解决,十分严密,在后续申请到来时说。
Connector 的启动次要目标就是提供好服务端的 serverSocket,指定好当有 socket 申请时,要依照协定去解决申请(Http11NioProtocol)