標籤:
原文連結 http://www.chepoo.com/zookeeper-session-timeout.html
1、會話概述
在ZooKeeper中,用戶端和服務端建立串連後,會話隨之建立,產生一個全域唯一的會話ID(Session ID)。
伺服器和用戶端之間維持的是一個長串連,在SESSION_TIMEOUT時間內,伺服器會確定用戶端是否
正常串連(用戶端會定時向伺服器發送heart_beat,伺服器重設下次SESSION_TIMEOUT時間)。
因此,在正常情況下,Session一直有效,並且ZK叢集所有機器上都儲存這個Session資訊。
在出現網路或其它問題情況下(例如用戶端所串連的那台ZK機器掛了,或是其它原因的網路閃斷),
用戶端與當前串連的那台伺服器之間串連斷了,這個時候用戶端會主動在
地址清單(執行個體化ZK對象的時候傳入構造方法的那個參數connectString)中選擇新的地址進行串連。
2、串連斷開
好了,上面基本就是伺服器與用戶端之間維持會話的過程了。在這個過程中,
使用者可能會看到兩類異常CONNECTIONLOSS(串連斷開)和SESSIONEXPIRED(Session到期)。
串連斷開(CONNECTIONLOSS)一般發生在網路的閃斷或是用戶端所已連線的服務器掛機的時候,
這種情況下,ZooKeeper用戶端自己會首先感知到這個異常,具體邏輯是在如下方法中觸發的:
1234567891011121314151617181920212223242526 |
void org.apache.zookeeper.ClientCnxn.SendThread.run(){ …… …… } catch (Throwable e) { if (closing) { if (LOG.isDebugEnabled()) { // closing so this is expected LOG.debug("An exception was thrown while closing send thread for session 0x" + Long.toHexString(getSessionId()) + " : " + e.getMessage()); } break; } else { // this is ugly, you have a better way speak up if (e instanceof SessionExpiredException) { LOG.info(e.getMessage() + ", closing socket connection"); } else if (e instanceof SessionTimeoutException) { LOG.info(e.getMessage() + RETRY_CONN_MSG); } else if (e instanceof EndOfStreamException) { LOG.info(e.getMessage() + RETRY_CONN_MSG); } else if (e instanceof RWServerFoundException) { LOG.info(e.getMessage()); } else { …… …… } |
一種情境是Server伺服器掛了,這個時候,ZK用戶端首選會捕獲異常,如下:
捕獲異常後,ZK用戶端會列印類似於如下日誌:
1 |
EndOfStreamException: Unable to read additional data from server sessionid 0x13ab17ad9ec000b, likely server has closed socket |
然後做一些socket串連的善後工作。接下去是用戶端重新選擇一個Server Ip嘗試串連,邏輯如代碼C2-1所示,
這裡主要就是從地址清單中擷取一個新的Server地址進行串連.
123456789101112131415161718192021222324252627 |
[C2-1] private void startConnect() throws IOException { state = States.CONNECTING; InetSocketAddress addr; if (rwServerAddress != null) { addr = rwServerAddress; rwServerAddress = null; } else { addr = hostProvider.next(1000); } LOG.info("Opening socket connection to server " + addr); setName(getName().replaceAll("\\(.*\\)", "(" + addr.getHostName() + ":" + addr.getPort() + ")")); try { zooKeeperSaslClient = new ZooKeeperSaslClient("zookeeper/"+addr.getHostName()); } catch (LoginException e) { LOG.warn("SASL authentication failed: " + e + " Will continue connection to Zookeeper server without " + "SASL authentication, if Zookeeper server allows it."); eventThread.queueEvent(new WatchedEvent( Watcher.Event.EventType.None, Watcher.Event.KeeperState.AuthFailed, null)); } clientCnxnSocket.connect(addr); } |
程式運行過程中,整個過程日誌列印大致如下:
12345678910111213141516 |
2012-10-31 09:09:57,379 - INFO [main-SendThread(test.zookeeper.connection_string2:2181):[email protected]] - Unable to read additional data from server sessionid 0x23ab45c87df0000, likely server has closed socket, closing socket connection and attempting reconnect 收到事件通知:Disconnected 擷取資料成功,path:/nileader 2012-10-31 09:09:58,293 - INFO [[email protected]] - Opening socket connection to server /1.2.1.1:2181 2012-10-31 09:09:58,294 - WARN [[email protected]] - SecurityException: java.lang.SecurityException: Unable to locate a login configuration occurred when trying to find JAAS configuration. 2012-10-31 09:09:58,295 - INFO [[email protected]] - Client will not SASL-authenticate because the default JAAS configuration section ‘Client‘ could not be found. If you are not using SASL, you may ignore this. On the other hand, if you expected SASL to work, please fix your JAAS configuration. 2012-10-31 09:09:58,296 - INFO [[email protected]] - Socket connection established to test.zookeeper.connection_string/1.2.1.1:2181, initiating session 2012-10-31 09:09:58,299 - INFO [[email protected]] - Session establishment complete on server test.zookeeper.connection_string/1.2.1.1:2181, sessionid = 0x23ab45c87df0000, negotiated timeout = 10000 收到事件通知:SyncConnected |
所以,現在對於“串連斷開”這個過程就一目瞭然了,核心流程如下:
ZK用戶端捕獲“串連斷開”異常 ——> 擷取一個新的ZK地址 ——> 嘗試串連
在這個流程中,我們可以發現,整個過程不需要開發人員額外的程式介入,都是ZK用戶端自己會進行的,
並且,使用的會話ID都是同一個,所以結論就是:發生CONNECTIONLOSS的情況,
應用不需要做什麼事情,等待ZK用戶端建立新的串連即可。
3、會話逾時
SESSIONEXPIRED發生在上面藍色文字部分,這個通常是ZK用戶端與伺服器的串連斷了,試圖串連上新的ZK機器,
但是這個過程如果耗時過長,超過了SESSION_TIMEOUT 後還沒有成功串連上伺服器,
那麼伺服器認為這個Session已經結束了(伺服器無法確認是因為其它異常原因還是用戶端主動結束會話),
由於在ZK中,很多資料和狀態都是和會話綁定的,一旦會話失效,那麼ZK就開始清除和這個會話有關的資訊,
包括這個會話建立的臨時節點和註冊的所有Watcher。在這之後,由於網路恢複後,用戶端可能會重新串連上伺服器,
但是很不幸,伺服器會告訴用戶端一個異常:SESSIONEXPIRED(會話到期)。此時用戶端的狀態變成 CLOSED狀態,
應用要做的事情就是的看自己應用的複雜程式了,要重新執行個體zookeeper對象,
然後重新操作所有臨時資料(包括臨時節點和註冊Watcher),總之,會話逾時在ZK使用過程中是真實存在的。
所以這裡也簡單總結下,一旦發生會話逾時,那麼儲存在ZK上的所有臨時資料與註冊的訂閱者都會被移除,
此時需要重新建立一個ZooKeeper用戶端執行個體,需要自己編碼做一些額外的處理。
4、會話時間(Session Time)
在《ZooKeeper API 使用》一文中已經提到,在執行個體化一個ZK用戶端的時候,需要設定一個會話的逾時時間。
這裡需要注意的一點是,用戶端並不是可以隨意設定這個會話逾時時間,在ZK伺服器端對會話逾時時間是有限制的,主
要是minSessionTimeout和maxSessionTimeout這兩個參數設定的。(詳細查看這個文章《ZooKeeper管理員指南》)Session逾時時間限制,
如果用戶端設定的逾時時間不在這個範圍,那麼會被強制設定為最大或最小時間。
預設的Session逾時時間是在2 * tickTime ~ 20 * tickTime。ZK實現如下,在這個方法中處理:
ZooKeeperServer.processConnectRequest(ServerCnxn cnxn, ByteBuffer incomingBuffer)
所以,如果應用對於這個會話逾時時間有特殊的需求的話,一定要和ZK管理員溝通好,確認好服務端是否設定了對會話時間的限制。
【轉】ZooKeeper 會話逾時