在高并發(fā)的項目中經(jīng)常遇到服務(wù)請求無法訪問的情況。本文從tomcat源碼對keep-alive的實現(xiàn)進行探討這個問題。
基礎(chǔ)知識
http請求的keep-alive
http1.1開始支持長連接。請求的頭部會帶上
Connection: Keep-Alive
長連接的作用是減少斷開連接和重新連接的開銷,提高網(wǎng)絡(luò)請求效率。http只是1個協(xié)議規(guī)范,具體的實現(xiàn)請見下文。
java的tcp網(wǎng)絡(luò)通信是通過socket進行。下面是示例代碼。
erverSocket serverSocket = new ServerSocket(8080, 1, InetAddress.getByName(“l(fā)ocalhost”));
Socket socket = null;
InputStream is = null;
OutputStream os = null;
try {
socket = serverSocket.accept();//1.監(jiān)聽到客戶端的連接
is = socket.getInputStream();
os = socket.getOutputStream();
Request request = Util.getRequest(is);//2.從輸入流中讀取數(shù)據(jù),并根據(jù)Http協(xié)議轉(zhuǎn)換成請求
Response response = Util.service(request);//服務(wù)器內(nèi)部根據(jù)請求信息給出響應(yīng)信息
os.writeResponse(response);//3.將響應(yīng)信息寫到輸出流
} catch (Exception e) {
e.printStackTrace();
} finally {//4.關(guān)閉輸入輸出流及連接
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
socket.close();
}
所以可以發(fā)現(xiàn)tcp連接由操作系統(tǒng)底層實現(xiàn),但http由的實現(xiàn)是由java程序?qū)崿F(xiàn)。我們?yōu)g覽器發(fā)送的請求為http,java的后端服務(wù)是典型的有tomcat。name我們可以看一下tomcat是如何實現(xiàn)http的
按圖索驥研究tomcat
linux下tomcat的啟動我們只要運行startup.sh
我們研究下這個文件,其中的內(nèi)容如下,運行startup.sh的實際結(jié)果是運行catalina.sh還帶了參數(shù)start
#略
EXECUTABLE=catalina.sh
#略
exec "$PRGDIR"/"$EXECUTABLE" start "$@"
接著我們看下catalina.sh,可以看到我們運行的java的入口類是org.apache.catalina.startup.Bootstrap,接著我們找Bootstrap源碼的main函數(shù),main函數(shù)啟動項目,開啟端口,處于等待狀態(tài)。main還解析了tomcat的server.xml文件,進行初始化。tomcat的處理http協(xié)議的類是Http11Processor。這個類有個service方法。
方法中有個while循環(huán),還有個keepAlive參數(shù)。其中有一段對keepalive的賦值改變
if (maxKeepAliveRequests == 1) {
keepAlive = false;
} else if (maxKeepAliveRequests > 0 && socketWrapper.decrementKeepAlive() <= 0) {
keepAlive = false;
}
這里的maxKeepAliveRequests 就是server配置文件里的
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxKeepAliveRequests = 10000 />
這個參數(shù)的意思是最多經(jīng)過多少個請求之后將Connection有keep-alive改為close的。
當(dāng)keepAlive設(shè)為false以后,會退出循環(huán)并返回SocketState.CLOSED給調(diào)用者。調(diào)用者收到這個狀態(tài)后會關(guān)閉socket。結(jié)束連接,tcp會進行4次揮手結(jié)束會話。
假設(shè)keepAlive一開始就未設(shè)置,那么就不會進入循環(huán),直接返回調(diào)用者SocketState.CLOSED。
如果keepAlive還沒到最大值,會一直在while的循環(huán)中,持續(xù)處理socket中的內(nèi)容,直到keepAlive失效,或者連接中斷。
了解了原理之后,我們來看看我們的問題。keepAlive開啟會一直占用一個連接,直到socket關(guān)閉。tomcat有最大連接數(shù)參數(shù)是maxConnections,這個值表示最多可以有多少個socket連接到tomcat上。BIO模式下默認(rèn)最大連接數(shù)是它的最大線程數(shù)(缺省是200),NIO模式下默認(rèn)是10000,APR模式則是8192(windows上則是低于或等于maxConnections的1024的倍數(shù))。如果設(shè)置為-1則表示不限制。當(dāng)請求過多時,新的請求不會被接受,老的請求受網(wǎng)絡(luò)io的影響。但老的請求的會話被關(guān)閉的可能性還比較小。所以在搶票之類的程序中,先登入服務(wù)所在的tomcat還是有優(yōu)勢的。從另一個方面來說關(guān)閉keepalive的功能對搶票人來說較公平。