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
,默認最多可緩存500個對象(見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 https://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。
最後更新:2017-05-23 17:02:58