No backend server available:沒(méi)有可用的后端服務(wù)器。最常見(jiàn)最可能的原因就是:后端server全部沒(méi)有啟動(dòng)或者Apache無(wú)法連接到其中的任何一個(gè)。復(fù)現(xiàn)方式很簡(jiǎn)單,只啟動(dòng)Apache,然后做后端weblogic server的業(yè)務(wù)請(qǐng)求,或者中斷Apache到后端服務(wù)器的網(wǎng)絡(luò)。這種原因很好檢查,也很好解決。本文中我們將探討另外一種情況,網(wǎng)絡(luò)沒(méi)有問(wèn)題,而且至少有一個(gè)服務(wù)器存活,但客戶端仍然碰到“No backend server available”。
HALF_OPEN_SOCKET_RETRY:Weblogic內(nèi)部自定的異常,這種異常通常由于Apache使用了某個(gè)已經(jīng)中斷的連接向后端服務(wù)器發(fā)送請(qǐng)求,這種請(qǐng)求肯定會(huì)失敗。連接中斷的原因也很多,比如我們關(guān)閉后端的服務(wù)器,那么Apache手里到這個(gè)服務(wù)器的連接再次被使用時(shí),Apache將會(huì)收到HALF_OPEN_SOCKET_RETRY。
首先,我們看一下plugin的分發(fā)機(jī)制。通常每個(gè)非初始請(qǐng)求都會(huì)在自己的cookie中有一個(gè)JSESSIONID,JSESSIONID由weblogic產(chǎn)生,用戶唯一標(biāo)識(shí)和該用戶相關(guān)的Session。JSessionID常見(jiàn)格式為:
SessionHash ! primaryHash ! SecondaryHash,比如:
s4wYKyvDKG2ZDHwQclchD50PYjvCsN9nLSL9hPRMQ2ZzM3pVdbL1!1198345223!-977705075
primary:該Session的主服務(wù)器
secondary:該Session的備份服務(wù)器,如果沒(méi)做SessionReplication,則沒(méi)有該列。如果SessionReplication失敗,那么該列會(huì)是none或null。
有了JSESSIONID,在load_utils.getPreferredServersFromCookie()中,plugin會(huì)解析這個(gè)JSESSIONID,如果我們打開(kāi)plugin的debug,我們會(huì)看到類(lèi)似如下的信息,
Thu Sep 10 08:56:36 2009 <1899912525369961> Found cookie from cookie header: JSESSIONID=s4wYKyvDKG2ZDHwQclchD50PYjvCsN9nLSL9hPRMQ2ZzM3pVdbL1!1198345223!-977705075
Thu Sep 10 08:56:36 2009 <1899912525369961> Parsing cookie JSESSIONID=s4wYKyvDKG2ZDHwQclchD50PYjvCsN9nLSL9hPRMQ2ZzM3pVdbL1!1198345223!-977705075
Thu Sep 10 08:56:36 2009 <1899912525369961> getpreferredServersFromCookie: [1198345223!-977705075]
Thu Sep 10 08:56:36 2009 <1899912525369961> primaryJVMID: [1198345223]
secondaryJVMID: [-977705075]
解析出了primaryJVMID、secondaryJVMID,這是plugin需要檢查這兩個(gè)id是否為空,如果兩個(gè)都為空,那么說(shuō)明這個(gè)請(qǐng)求沒(méi)有prefer server,plugin只要按照最基本的load balance策略作請(qǐng)求分發(fā)即可。如果至少有一個(gè)不為空,我們繼續(xù)找該session所prefer的server,debug 信息如下:
Thu Sep 10 08:56:36 2009 <1899912525369961>Primary or Secondary JVMID *not* found in cookie, ignore preferred servers //說(shuō)明沒(méi)有從cookie中找到primay或secondary。
Thu Sep 10 08:56:36 2009 <1899912525369961> No of JVMIDs found in cookie: 2 //說(shuō)明找到了兩個(gè)jvmID
Apache plugin運(yùn)行過(guò)程中會(huì)維護(hù)一個(gè)server list,這個(gè)server list就是我們?cè)趆ttpd.conf中指定的server列表,如果我們使用了dynamic server list,那么這個(gè)list是隨著后端cluster的變化而動(dòng)態(tài)更新的。server list中每個(gè)server有自己的JVMID。plugin從cookie中解析出primaryJVMID、secondaryJVMID后,需要primaryJVMID、secondary JVMID做校驗(yàn),防止cookie中的信息過(guò)于陳舊(如果后端服務(wù)器發(fā)生過(guò)重起,就會(huì)出現(xiàn)JVMID不一致的情況)。校驗(yàn)過(guò)程其實(shí)很簡(jiǎn)單,plugin會(huì)遍歷手里的server list。如果發(fā)現(xiàn)某個(gè)server被標(biāo)志為bad,它會(huì)檢查skip-time,如果skip-time(默認(rèn)10秒)已到,即從這個(gè)server被mark bad到現(xiàn)在已經(jīng)超過(guò)10秒,那么plugin會(huì)把這個(gè)server重新mark good(這樣如果我們接下來(lái)創(chuàng)建到這個(gè)server的連接成功,那這個(gè)server就真的可用了,plugin通過(guò)這種機(jī)制來(lái)恢復(fù)后端server)。
1 time_t t = lists[i].startMarkBad;
2 if (t > 0) {
3 time_t now = 0;
4 time(&now);
5 int delta = now - t;
6 if (delta >= MAX_SKIP_TIME) {
7 lists[i].startMarkBad = 0;
8 lists[i].isGood = 1;
9 }
10 }
2 if (t > 0) {
3 time_t now = 0;
4 time(&now);
5 int delta = now - t;
6 if (delta >= MAX_SKIP_TIME) {
7 lists[i].startMarkBad = 0;
8 lists[i].isGood = 1;
9 }
10 }
接著plugin會(huì)將解析出來(lái)的primaryJVMID、secondaryJVMID后自己server list中server的JVMID做比對(duì)。如果primary匹配,就把對(duì)應(yīng)的serverInfo設(shè)入請(qǐng)求的preferred[0],同樣如果seconday匹配,對(duì)應(yīng)的serverInfo會(huì)被設(shè)入請(qǐng)求的preferred[1]。注意:接下來(lái)plugin將以preferred為基礎(chǔ)進(jìn)行分發(fā)、failover。debug信息如下:
Wed Sep 02 14:33:12 2009 <553612518731924> getPreferredFromCookie: Found Primary 172.23.4.53:8001:0
Wed Sep 02 14:33:12 2009 <553612518731924> getPreferredFromCookie: Found Secondary 172.23.4.52:8001:0
如果匹配的server數(shù)目和cookie中jvmid數(shù)一致,則還能看到如下輸出:
Wed Sep 02 14:33:12 2009 <553612518731924> getPreferredFromCookie: Found 2 servers
如果不匹配,那么輸出信息將是下面這樣:
Wed Sep 02 14:33:12 2009 <553612518731924> getPreferredFromCookie: Found atleast 1 server
如果匹配結(jié)果發(fā)現(xiàn)沒(méi)有一致的JVMID,如下:
Thu Sep 10 08:56:36 2009 <1899912525369961> getPreferredFromCookie: Either JVMIDs not set or they are stale. Will try to get JVMIDs from WLS
那么這時(shí)候plugin就需要強(qiáng)制更新自己server list中server的JVMID,這個(gè)更新通過(guò)proxy.initJVMID()實(shí)現(xiàn)。initJVMID中,plugin遍歷整個(gè)server list,嘗試創(chuàng)建到每個(gè)server的連接,如果連接創(chuàng)建失敗,這個(gè)server會(huì)被mark bad。連接創(chuàng)建成功,plugin向后端server發(fā)送internal request,如下:
Thu Sep 10 08:56:36 2009 <1899912525369961> ======internal request /bea_wls_internal/ WLDummyInitJVMIDs======
如果連接能夠成功創(chuàng)建,但發(fā)送請(qǐng)求的時(shí)候發(fā)生HALF_OPEN_SOCKET_RETRY(這里為什么會(huì)發(fā)送HALF_OPEN_SOCKET_RETRY,后面我們會(huì)討論到),這個(gè)server也會(huì)被mark bad(這里對(duì)HALF_OPEN_SOCKET_RETRY的處理不同于應(yīng)用請(qǐng)求),如下:
Thu Sep 10 08:56:36 2009 <1899912525369961> readStatus: Local port of the socket 53149, connected to Remote Host/Port 172.23.4.53/8001
Thu Sep 10 08:56:36 2009 <1899912525369961> readStatus: Response contains no data - isRecycled: 0
Thu Sep 10 08:56:36 2009 <1899912525369961> *******Exception type [HALF_OPEN_SOCKET_RETRY] (Unexpected EOF reading HTTP status - request retry) raised at line 854 of ../nsapi/URL.cpp
Thu Sep 10 08:56:36 2009 <1899912525369961> initJVMID: Failed to retrieved JVMID for 172.23.4.53:8001:8001
Thu Sep 10 08:56:36 2009 <1899912525369961> initJVMID: Marked server as BAD
如果請(qǐng)求處理成功,將會(huì)看到類(lèi)似于以下的信息:
Thu Sep 10 08:56:36 2009 <1899912525369961> ServerInfo struct for JVMID '-977705075' populated
Server Details are:
OrigHostInfo [172.23.4.52]
isOrigHostInfoDNS [0]
Host [172.23.4.52]
Port [8001]
SecurePort [8201]
Thu Sep 10 08:56:36 2009 <1899912525369961> Initializing lastIndex=0 for a list of length=1
initJVMID()除了更新jvmID,同時(shí)還給我們這個(gè)請(qǐng)求分配了preferred server list。
獲取到請(qǐng)求的preferred后,程序的控制權(quán)將回到proxy.wl_proxy(),這個(gè)方法是所有http請(qǐng)求的入口。回到wl_proxy后,進(jìn)入while循環(huán),循環(huán)的終止條件就是我們的retry次數(shù)(默認(rèn)5次)。進(jìn)入循環(huán)后,debug信息如下:
Thu Sep 10 08:56:36 2009 <1899912525369961> attempt #0 out of a max of 5
plugin要成功發(fā)送一個(gè)http請(qǐng)求,首先需要拿到connection,如果我們?cè)O(shè)置了keep-alive,首先會(huì)從connection pool中獲取,如果沒(méi)有,則創(chuàng)建新的物理連接。debug信息分別如下:
Wed Sep 02 14:35:45 2009 <553612518733456> got a pooled connection to preferred server '172.23.4.53/8001for '/test_sleep/testsleep.jsp', Local port:3651
Wed Sep 02 14:33:12 2009 <553612518731924> created a new connection to preferred server '172.23.4.53/8001' for '/test_sleep/testsleep.jsp', Local port:3636
拿到connection后,plugin會(huì)將request的header信息發(fā)送到server端,并從連接上讀取response。此時(shí)上面兩種連接都可能出現(xiàn)HALF_OPEN_SOCKET_RETRY。我們分別看看這兩種情況:
1:如果pool connection對(duì)應(yīng)的后端服務(wù)器已經(jīng)被shutdown,那么我們那pool connection做操作的時(shí)候,肯定無(wú)法完成,因此很容易碰到HALF_OPEN_SOCKET_RETRY。碰到HALF_OPEN_SOCKET_RETRY,plugin會(huì)做retry,而不是直接failover。重新去getConnection,如果pool中corrupted connection足夠多,那么五次失敗后,客戶端最終會(huì)看到“No backend server available”,debug中會(huì)有如下信息:
Tue Sep 1 10:43:41 2009 <10088125176582121> request [/socs/favicon.ico] did NOT process successfully..................
2:pool中的connection不是很多,比如2個(gè),那么我們做完兩次retry后,這是就要?jiǎng)?chuàng)建物理的連接,如果連接創(chuàng)建失敗,這個(gè)server會(huì)立刻mark bad,請(qǐng)求將被failover到secondary,debug信息如下:
Wed Sep 02 14:36:06 2009 <553612518733456> *******Exception type [CONNECTION_REFUSED] (Error connecting to host 10.182.216.198:8002) raised at line 1732 of ../nsapi/URL.cpp
如果創(chuàng)建連接成功,但sendRequestHeader的時(shí)候發(fā)生HALF_OPEN_SOCKET_RETRY,那么plugin同樣會(huì)繼續(xù)作retry,而不是failover。如果剩余幾次retry結(jié)果都一樣(創(chuàng)建連接成功,但sendRequestHeader失敗),客戶最終也會(huì)看到“No backend server available。日志通常如下:
Tue Sep 1 10:43:41 2009 <10088125176582121> attempt #5 out of a max of 5
Tue Sep 1 10:43:41 2009 <10088125176582121> created a new connection to preferred server '172.23.4.52/8001' for '/socs/favicon.ico', Local port:49416
......
Tue Sep 1 10:43:41 2009 <10088125176582121> URL::sendHeaders(): meth='GET' file='/socs/favicon.ico' protocol='HTTP/1.1'
......
Tue Sep 1 10:43:41 2009 <10088125176582121> readStatus: Local port of the socket 49416, connected to Remote Host/Port 172.23.4.52/8001
Tue Sep 1 10:43:41 2009 <10088125176582121> readStatus: Response contains no data - isRecycled: 0
Tue Sep 1 10:43:41 2009 <10088125176582121> *******Exception type [HALF_OPEN_SOCKET_RETRY] (Unexpected EOF reading HTTP status - request retry) raised at line 854 of ../nsapi/URL.cpp
Tue Sep 1 10:43:41 2009 <10088125176582121> got exception in sendRequest phase: HALF_OPEN_SOCKET_RETRY: [os error=0, line 854 of ../nsapi/URL.cpp]: Unexpected EOF reading HTTP status - request retry at line 3080
對(duì)于1,這個(gè)可以理解,如果pool中connection被關(guān)閉(keep-alive超時(shí))完了,客戶就不會(huì)碰到這樣的問(wèn)題問(wèn)題了。而對(duì)于2,更多的應(yīng)該說(shuō)是OS的問(wèn)題,OS沒(méi)有正確釋放端口。如果端口釋放成功,plugin做url.connect()的時(shí)候,應(yīng)該是收到connection refuse,而不是正常創(chuàng)建連接(實(shí)際上是無(wú)用連接,使用時(shí)出現(xiàn)Socket_Half_Open)。對(duì)于2,我們可以通過(guò)以下的方法檢查:
1:ps -ef | grep java,檢查對(duì)應(yīng)的managed server時(shí)候shutdown
2:netstat -a | grep listening port,如果server已經(jīng)shutdown了,卻還能看到listening port,那么就是OS的問(wèn)題了。
寫(xiě)了這么多,覺(jué)得比較繁瑣,不知道大家能否看得明白,建議最好拿一個(gè)wl_proxy的debug log,對(duì)照著看。