轉自晴天哥_374的簡書
開篇
這篇文章的主要目的是分析下Tomcat在處理連接請求的整個過程,參考了前人的文章並在文末指出,通過時序圖能夠較清楚的走通整個流程。
Tomcat處理流程
Tomcat處理流程
說明:
Connector 啓動以後會啓動一組線程用於不同階段的請求處理過程,Acceptor、Poller、worker 所在的線程組都維護在 NioEndpoint 中。
-
- Acceptor線程組。用於接受新連接,並將新連接封裝一下,選擇一個 Poller 將新連接添加到 Poller 的事件隊列中,Acceptor線程組是多個線程組成的線程組。
-
- Poller 線程組。用於監聽 Socket 事件,當 Socket 可讀或可寫等等時,將 Socket 封裝一下添加到 worker 線程池的任務隊列中,Poller線程組是多個線程組成的線程組。
-
- worker 線程組。用於對請求進行處理,包括分析請求報文並創建 Request 對象,調用容器的 pipeline 進行處理,worker線程組是Executor創建的線程池。
public class NioEndpoint extends AbstractJsseEndpoint<NioChannel> {
public void startInternal() throws Exception {
// 創建worker線程組
if ( getExecutor() == null ) {
createExecutor();
}
// Poller線程組由一堆線程組成
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
startAcceptorThreads();
}
}
}
public abstract class AbstractEndpoint<S> {
// Acceptor線程組由一堆線程組成
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount();
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor();
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
}
// worker的線程組由executor創建線程池組成
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);
}
}
請求處理過程分解
Acceptor接收連接過程
Acceptor.jpg
說明:
Acceptor接受的新連接沒有立即註冊到selector當中,需要先封裝成PollerEvent對象後保存至PollerEvent隊列當中,Poller對象會消費PollerEvent隊列,類似生產消費模型。
-
- Acceptor 在啓動後會阻塞在 ServerSocketChannel.accept(); 方法處,當有新連接到達時,該方法返回一個 SocketChannel。
-
- setSocketOptions()方法將 Socket 封裝到 NioChannel 中,並註冊到 Poller。
-
- 我們一開始就啓動了多個 Poller 線程,註冊的時候採用輪詢選擇 Poller 。NioEndpoint 維護了一個 Poller 數組,當一個連接分配給 pollers[index] 時,下一個連接就會分配給 pollers[(index+1)%pollers.length]。
-
- addEvent() 方法會將 Socket 添加到該 Poller 的 PollerEvent 隊列中。到此 Acceptor 的任務就完成了。
public class NioEndpoint extends AbstractJsseEndpoint<NioChannel> {
private volatile ServerSocketChannel serverSock = null;
protected class Acceptor extends AbstractEndpoint.Acceptor {
public void run() {
while (running) {
state = AcceptorState.RUNNING;
try {
SocketChannel socket = null;
try {
// 監聽socket負責接收新連接
socket = serverSock.accept();
} catch (IOException ioe) {
}
if (running && !paused) {
// 處理接受到的socket對象
if (!setSocketOptions(socket)) {
closeSocket(socket);
}
}
} catch (Throwable t) {
}
}
state = AcceptorState.ENDED;
}
}
protected boolean setSocketOptions(SocketChannel socket) {
try {
socket.configureBlocking(false);
Socket sock = socket.socket();
socketProperties.setProperties(sock);
channel = new NioChannel(socket, bufhandler);
// 註冊到Poller當中
getPoller0().register(channel);
} catch (Throwable t) {
}
return true;
}
public Poller getPoller0() {
int idx = Math.abs(pollerRotater.incrementAndGet()) % pollers.length;
return pollers[idx];
}
public class Poller implements Runnable {
public void register(final NioChannel socket) {
socket.setPoller(this);
NioSocketWrapper ka = new NioSocketWrapper(socket, NioEndpoint.this);
r = new PollerEvent(socket,ka,OP_REGISTER);
// 添加PollerEvent隊列當中
addEvent(r);
}
private void addEvent(PollerEvent event) {
// 投入到PollerEvent隊列當中
events.offer(event);
if ( wakeupCounter.incrementAndGet() == 0 ) selector.wakeup();
}
}
}
Poller處理請求
Poller.jpg
說明:
Poller會消費PollerEvent隊列(由Acceptor進行投遞),並註冊到Selector當中。當註冊到Selector的socket數據可讀的時候將socket封裝成SocketProcessor對象,投遞到Executor實現的線程池進行處理。
-
- selector.select(1000)。當 Poller 啓動後因爲 selector 中並沒有已註冊的 Channel,所以當執行到該方法時只能阻塞。所有的 Poller 共用一個 Selector,其實現類是 sun.nio.ch.SelectorImpl。
-
- events() 方法通過 addEvent() 方法添加到事件隊列中的 Socket 註冊到SelectorImpl。這裏指的socket是accept過來的請求的socket。
-
- 當 Socket 可讀時,Poller 纔對其進行處理,createSocketProcessor() 方法將 Socket 封裝到 SocketProcessor 中,SocketProcessor 實現了 Runnable 接口。worker 線程通過調用其 run() 方法來對 Socket 進行處理。
-
- execute(SocketProcessor) 方法將 SocketProcessor 提交到線程池,放入線程池的 workQueue 中。workQueue 是 BlockingQueue 的實例。
public class NioEndpoint extends AbstractJsseEndpoint<NioChannel> {
public static class PollerEvent implements Runnable {
private NioChannel socket;
private int interestOps;
private NioSocketWrapper socketWrapper;
public PollerEvent(NioChannel ch, NioSocketWrapper w, int intOps) {
reset(ch, w, intOps);
}
public void run() {
if (interestOps == OP_REGISTER) {
try {
socket.getIOChannel().register(
socket.getPoller().getSelector(), SelectionKey.OP_READ, socketWrapper);
} catch (Exception x) {
}
}
}
}
public class Poller implements Runnable {
public void run() {
while (true) {
// events()負責處理PollerEvent事件並註冊到selector當中
hasEvents = events();
keyCount = selector.select(selectorTimeout);
// 處理新接受的socket的讀寫事件
Iterator<SelectionKey> iterator =
keyCount > 0 ? selector.selectedKeys().iterator() : null;
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
processKey(sk, attachment);
}
}
}
// 處理讀寫事件
protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
if (sk.isReadable()) {
if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {
closeSocket = true;
}
}
if (!closeSocket && sk.isWritable()) {
if (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {
closeSocket = true;
}
}
}
}
}
public abstract class AbstractEndpoint<S> {
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
try {
sc = createSocketProcessor(socketWrapper, event);
Executor executor = getExecutor();
// 註冊到Worker的線程池ThreadPoolExecutor。
if (dispatch && executor != null) {
executor.execute(sc);
}
} catch (RejectedExecutionException ree) {
}
return true;
}
}
Worker處理具體請求
Worker.jpg
說明:
-
- 當新任務添加到 workQueue(ThreadPoolExecutor)後,workQueue.take()方法會返回一個 Runnable,通常是 SocketProcessor,然後 worker 線程調用 SocketProcessor的run() -> doRun()方法對 Socket 進行處理。
-
- createProcessor() 會創建一個Http11Processor, 它用來解析 Socket,將 Socket 中的內容封裝到Request中。注意這個Request是臨時使用的一個類,它的全類名是org.apache.coyote.Request。
-
- CoyoteAdapter的postParseRequest()方法封裝一下 Request,並處理一下映射關係(從 URL 映射到相應的 Host、Context、Wrapper)。
-
- CoyoteAdapter將 Rquest 提交給 Container(StandardEngine) 處理之前,並將 org.apache.coyote.Request封裝到 org.apache.catalina.connector.Request,傳遞給 Container處理的 Request 是 org.apache.catalina.connector.Request。
-
- connector.getService().getMapper().map(),用來在Mapper中查詢 URL 的映射關係。映射關係會保留到 org.apache.catalina.connector.Request 中,Container處理階段 request.getHost()是使用的就是這個階段查詢到的映射主機,以此類推 request.getContext()、request.getWrapper()都是。
-
- connector.getService().getContainer().getPipeline().getFirst().invoke()會將請求傳遞到 Container(StandardEngine)處理,至此進入了Engine->Host->Context->Wrapper的處理流程,當然了 Container處理也是在 Worker線程中執行的(也就是說Tomcat處理請求是通過ThreadPoolExecutor的線程池實現的),但是這是一個相對獨立的模塊,所以單獨分出來一節。
Container單個請求處理流程
StandardEngineValve
說明:
-
- 每個容器(Engine、Host、Context、Wrapper)的 StandardPipeline 上都會有多個已註冊的 Valve,我們只關注每個容器的 Basic Valve,其他 Valve 都是在 Basic Valve 前執行。
-
- request.getHost().getPipeline().getFirst().invoke() 先獲取對應的 StandardHost,並執行其 pipeline。
-
- request.getContext().getPipeline().getFirst().invoke() 先獲取對應的 StandardContext,並執行其 pipeline。
-
- request.getWrapper().getPipeline().getFirst().invoke() 先獲取對應的 StandardWrapper,並執行其 pipeline。
-
- StandardWrapper的Basic Valve是StandardWrapperValve,通過allocate() 用來加載並初始化 Servlet,值的一提的是 Servlet 並不都是單例的,當 Servlet 實現了 SingleThreadModel 接口後,StandardWrapper 會維護一組 Servlet 實例,這是享元模式。當然了 SingleThreadModel在 Servlet 2.4 以後就棄用了。
-
- createFilterChain() 方法會從 StandardContext 中獲取到所有的過濾器,然後將匹配 Request URL 的所有過濾器挑選出來添加到 filterChain 中。
doFilter() 執行過濾鏈,當所有的過濾器都執行完畢後調用 Servlet 的 service() 方法。
- createFilterChain() 方法會從 StandardContext 中獲取到所有的過濾器,然後將匹配 Request URL 的所有過濾器挑選出來添加到 filterChain 中。