Tomcat-connector的微調(3): processorCache與socket.processorCache
tomcat在處理每個連線時,Acceptor
角色負責將socket上下文封裝為一個任務SocketProcessor
然後提交給執行緒池處理。在BIO和APR模式下,每次有新請求時,會建立一個新的SocketProcessor
例項(在之前的tomcat對keep-alive的實現邏輯裡也介紹過可以簡單的通過SocketProcessor
與SocketWrapper
例項數對比socket的複用情況);而在NIO裡,為了追求效能,對SocketProcessor
也做了cache,用完後將物件狀態清空然後放入cache,下次有新的請求過來先從cache裡獲取物件,獲取不到再建立一個新的。
這個cache是一個ConcurrentLinkedQueue
SocketProperties
)。可以通過socket.processorCache
來設定這個快取的大小,注意這個引數是NIO特有的。
接下來在SocketProcessor
執行過程中,真正的業務邏輯是通過一個org.apache.coyote.Processor
的介面來封裝的,預設這個Processor
的實現是org.apache.coyote.http11.Http11Processor
。我們看一下SocketProcessor.process(...)
方法的大致邏輯:
public SocketState process(SocketWrapper<S> wrapper, SocketStatus status) { ... // 針對長輪詢或upgrade情況 Processor<S> processor = connections.get(socket); ... if (processor == null) { // 1) 嘗試從回收佇列裡獲取物件 processor = recycledProcessors.poll(); } if (processor == null) { // 2) 沒有再建立新的 processor = createProcessor(); } ... state = processor.process(wrapper); ... release(wrapper, processor, ...); ... return SocketState.CLOSED; }
上面的方法是在AbstractProtocol
模板類裡,所以BIO/APR/NIO都走這段邏輯,這裡使用了一個回收佇列來快取Processor
,這個回收佇列是ConcurrentLinkedQueue
的一個子類,佇列的長度可通過server.xml裡connector節點的processorCache
屬性來設定,預設值是200,如果不做限制的話可以設定為-1,這樣cache的上限將是最大連線數maxConnections
的大小。
在原有的一張ppt上加工了一下把這兩個快取佇列所在位置標示了一下,圖有點亂,重點是兩個綠顏色的cache佇列:
圖中位於上面的socket.processorCache
佇列是NIO獨有的,下面的processorCache
processorCache
這個引數在併發量比較大的情況下也蠻重要的,如果設定的太小,可能引起瓶頸。我們模擬一下,看看這個瓶頸是怎麼回事。先修改server.xml裡的connector節點,把processorCache
設定為0:
<Connector port="7001"
protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="20000"
redirectPort="8443"
processorCache="0"/>
啟動tomcat後,使用ab模擬併發請求:
$ ab -n100000 -c10 http://localhost:7001/main
然後在ab的執行過程中立刻執行jstack觀察堆疊資訊,會發現一大半執行緒阻塞在AbstractConnectionHandler.register
或AbstractConnectionHandler.unregister
方法上:
"http-nio-7001-exec-11" #34 daemon prio=5 os_prio=31 tid=0x00007fd05ab05000 nid=0x8903 waiting for monitor entry [0x000000012b3b7000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.register(AbstractProtocol.java:746)
- waiting to lock <0x00000007403b8950> (a org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.createProcessor(Http11NioProtocol.java:277)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.createProcessor(Http11NioProtocol.java:139)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:585)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1720)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1679)
...
"http-nio-7001-exec-4" #27 daemon prio=5 os_prio=31 tid=0x00007fd0593e3000 nid=0x7b03 waiting for monitor entry [0x000000012aca2000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.unregister(AbstractProtocol.java:773)
- locked <0x00000007403b8950> (a org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler)
at org.apache.coyote.AbstractProtocol$RecycledProcessors.offer(AbstractProtocol.java:820)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.release(Http11NioProtocol.java:219)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:690)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1720)
register
和unregister
分別是在建立和回收processor的時候呼叫的;看一下createProcessor
方法裡的大致邏輯:
public Http11NioProcessor createProcessor() {
Http11NioProcessor processor = new Http11NioProcessor(...);
processor.setXXX(...);
...
// 這裡,註冊到jmx
register(processor);
return processor;
}
tomcat對jmx支援的非常好,執行時資訊也有很多可以通過jmx獲取,所以在每個新連線處理的時候,會在建立processor物件的時候註冊一把,然後在processor處理完回收的時候再反註冊一把;但這兩個方法的實現都是同步的,同步的鎖是一個全域性的ConnectionHandler
物件,造成了多個執行緒會在這裡序列。
絕大部分應用沒有特別高的訪問量,通常並不需要調整processorCache
引數,但對於閘道器或代理一類的應用(尤其是使用servlet3的情況)這個地方可以設定的大一些,比如調到1000或者-1。