本文爲篤行平常工做記錄,爛筆頭系列。html
源碼前面,了無祕密 — by 侯傑java
近期的一個C++項目裏使用了Zookeeper作服務發現,期間遇到了SessionTimeOut問題的困擾,明明經過zookeeper c client設置了超時時間,但無效。c++
請原諒我一開始對zookeeper不熟悉。最終經過分析源碼瞭解到SessionTimeOut最終的肯定是一個協商的過程,而不是簡單的配置生效。git
在這裏記錄下Session超時時間的有關分析,基於zookeeper 3.4.8github
項目中使用的是 C client,經過zookeer_init
建立zk session,調用了zookeeper_init其實就開始了創建連接到ZK集羣的過程,這裏設置的recv_timeout 爲客戶端所指望的session超時時間,單位爲毫秒。apache
ZOOAPI zhandle_t *zookeeper_init(const char *host, watcher_fn fn, int recv_timeout, const clientid_t *clientid, void *context, int flags);
鏈接成功以後客戶端發起握手協議,能夠看到以前設置的recv_timeout隨握手協議一塊兒發送給服務端,zookeeper.c#L1485。c#
static int prime_connection(zhandle_t *zh) { int rc; /*this is the size of buffer to serialize req into*/ char buffer_req[HANDSHAKE_REQ_SIZE]; int len = sizeof(buffer_req); int hlen = 0; struct connect_req req; req.protocolVersion = 0; req.sessionId = zh->seen_rw_server_before ? zh->client_id.client_id : 0; req.passwd_len = sizeof(req.passwd); memcpy(req.passwd, zh->client_id.passwd, sizeof(zh->client_id.passwd)); req.timeOut = zh->recv_timeout; <-這裏設置timeOut req.lastZxidSeen = zh->last_zxid; req.readOnly = zh->allow_read_only; hlen = htonl(len); /* We are running fast and loose here, but this string should fit in the initial buffer! */ rc=zookeeper_send(zh->fd, &hlen, sizeof(len)); serialize_prime_connect(&req, buffer_req); rc=rc<0 ? rc : zookeeper_send(zh->fd, buffer_req, len); if (rc<0) { return handle_socket_error_msg(zh, __LINE__, ZCONNECTIONLOSS, "failed to send a handshake packet: %s", strerror(errno)); }
再來看看處理握手協議Resp的邏輯 zookeeper.c L1767session
static int check_events(zhandle_t *zh, int events) { if (zh->fd == -1) return ZINVALIDSTATE; …… …… …… deserialize_prime_response(&zh->primer_storage, zh->primer_buffer.buffer); /* We are processing the primer_buffer, so we need to finish * the connection handshake */ oldid = zh->client_id.client_id; newid = zh->primer_storage.sessionId; if (oldid != 0 && oldid != newid) { zh->state = ZOO_EXPIRED_SESSION_STATE; errno = ESTALE; return handle_socket_error_msg(zh,__LINE__,ZSESSIONEXPIRED, "sessionId=%#llx has expired.",oldid); } else { zh->recv_timeout = zh->primer_storage.timeOut; //設置爲Resp的Timeout zh->client_id.client_id = newid; }
至此能夠發現,最終客戶端的SessionTimeOut時間實際是通過服務端下發以後的,並不必定是最早設置的。socket
來看看服務端握手的處理邏輯ZooKeeperServer.java#L876。ide
public void processConnectRequest(ServerCnxn cnxn, ByteBuffer incomingBuffer) throws IOException { BinaryInputArchive bia = BinaryInputArchive.getArchive(new ByteBufferInputStream(incomingBuffer)); ConnectRequest connReq = new ConnectRequest(); connReq.deserialize(bia, "connect"); …… …… …… //根據客戶端上報的timeout和服務端自身的minSessionTimeOut。 //若是上報的timeout小於minSessionTimeOut則 設置timeout爲minSessionTimeOut. //若是上報的timeout大於maxSessionTimeOut則 設置timeout爲maxSessionTimeOut. //若是介於兩則之間,則以上報的時間爲準。 int sessionTimeout = connReq.getTimeOut(); byte passwd[] = connReq.getPasswd(); int minSessionTimeout = getMinSessionTimeout(); if (sessionTimeout < minSessionTimeout) { sessionTimeout = minSessionTimeout; } int maxSessionTimeout = getMaxSessionTimeout(); if (sessionTimeout > maxSessionTimeout) { sessionTimeout = maxSessionTimeout; } cnxn.setSessionTimeout(sessionTimeout); …… …… …… }
能夠一句話歸納,客戶端上報的指望timeout必定要在服務端設置的上下界之間,若是越過邊界,則以邊界爲準。
繼續看ZooKeeperServer.java#L104和ZooKeeperServer.java#L791
public static final int DEFAULT_TICK_TIME = 3000; protected int tickTime = DEFAULT_TICK_TIME; /** value of -1 indicates unset, use default */ protected int minSessionTimeout = -1; /** value of -1 indicates unset, use default */ protected int maxSessionTimeout = -1; protected SessionTracker sessionTracker;
tickTime爲3000毫秒,minSessionTimeOut和maxSessionTimeOut缺省值爲-1
public int getTickTime() { return tickTime; } public void setTickTime(int tickTime) { LOG.info("tickTime set to " + tickTime); this.tickTime = tickTime; } public int getMinSessionTimeout() { return minSessionTimeout == -1 ? tickTime * 2 : minSessionTimeout; //若是minSessionTimeOut爲缺省值這設置minSessionTimeOut爲2倍tickTime } public void setMinSessionTimeout(int min) { LOG.info("minSessionTimeout set to " + min); this.minSessionTimeout = min; } public int getMaxSessionTimeout() { return maxSessionTimeout == -1 ? tickTime * 20 : maxSessionTimeout; //若是maxSessionTimeout爲缺省值則設置maxSessionTimeout爲20倍tickTime } public void setMaxSessionTimeout(int max) { LOG.info("maxSessionTimeout set to " + max); this.maxSessionTimeout = max; }
能夠知道minSessionTimeOut和maxSessionTimeOut在缺省的時候則跟tickTime有關,分別爲2和20倍tickTime,繼續分析。ZooKeeperServer.java#L160
public ZooKeeperServer(FileTxnSnapLog txnLogFactory, int tickTime, int minSessionTimeout, int maxSessionTimeout, DataTreeBuilder treeBuilder, ZKDatabase zkDb) { serverStats = new ServerStats(this); this.txnLogFactory = txnLogFactory; this.zkDb = zkDb; this.tickTime = tickTime; this.minSessionTimeout = minSessionTimeout; this.maxSessionTimeout = maxSessionTimeout; LOG.info("Created server with tickTime " + tickTime + " minSessionTimeout " + getMinSessionTimeout() + " maxSessionTimeout " + getMaxSessionTimeout() + " datadir " + txnLogFactory.getDataDir() + " snapdir " + txnLogFactory.getSnapDir()); }
tickTime、minSessionTimeOut、maxSessionTimeOut實際構造函數傳入,固然還有一個無參構造函數以及一些setter和getter能夠設置這幾個參數。
繼續分析ZooKeeperServerMain.java#L94
public void runFromConfig(ServerConfig config) throws IOException { LOG.info("Starting server"); FileTxnSnapLog txnLog = null; try { // Note that this thread isn't going to be doing anything else, // so rather than spawning another thread, we will just call // run() in this thread. // create a file logger url from the command line args ZooKeeperServer zkServer = new ZooKeeperServer(); txnLog = new FileTxnSnapLog(new File(config.dataLogDir), new File( config.dataDir)); zkServer.setTxnLogFactory(txnLog); zkServer.setTickTime(config.tickTime); //咱們能夠發現實際運行的幾個參數除了默認值之外,能夠經過配置文件來配置生效。 zkServer.setMinSessionTimeout(config.minSessionTimeout); zkServer.setMaxSessionTimeout(config.maxSessionTimeout); cnxnFactory = ServerCnxnFactory.createFactory(); cnxnFactory.configure(config.getClientPortAddress(), config.getMaxClientCnxns()); cnxnFactory.startup(zkServer); cnxnFactory.join(); if (zkServer.isRunning()) { zkServer.shutdown(); } } catch (InterruptedException e) { // warn, but generally this is ok LOG.warn("Server interrupted", e); } finally { if (txnLog != null) { txnLog.close(); } } }
到此問題就明瞭了,咱們能夠經過配置來修改SessionTimeOut,默認配置文件只配置了tickTime,以下。
# The number of milliseconds of each tick tickTime=2000 # The number of ticks that the initial # synchronization phase can take initLimit=10 # The number of ticks that can pass between # sending a request and getting an acknowledgement syncLimit=5 # the directory where the snapshot is stored. # do not use /tmp for storage, /tmp here is just # example sakes. dataDir=/tmp/zookeeper # the port at which the clients will connect clientPort=2181 # the maximum number of client connections. # increase this if you need to handle more clients #maxClientCnxns=60 # # Be sure to read the maintenance section of the # administrator guide before turning on autopurge. # # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance # # The number of snapshots to retain in dataDir #autopurge.snapRetainCount=3 # Purge task interval in hours # Set to "0" to disable auto purge feature #autopurge.purgeInterval=1
通過源碼分析,得出SessionTimeOut的協商以下:
狀況1: 配置文件配置了maxSessionTimeOut和minSessionTimeOut
最終SessionTimeOut,必須在minSessionTimeOut和maxSessionTimeOut區間裏,若是跨越上下界,則以跨越的上屆或下界爲準。
狀況2:配置文件沒有配置maxSessionTimeOut和minSessionTimeOut
maxSessionTimeout沒配置則 maxSessionTimeOut設置爲 20 * tickTime
minSessionTimeOut沒配置則 minSessionTimeOut設置爲 2 * tickTime
也就是默認狀況下, SessionTimeOut的合法範圍爲 4秒~40秒,默認配置中tickTime爲2秒。
若是tickTime也沒配置,那麼tickTime缺省爲3秒。
遇到問題從源碼分析必定是最好的,能使得理解更深刻記憶更深入。
最後 ending...若有不足請指點,亦可留言或聯繫 fobcrackgp@163.com.
本文爲篤行原創文章首發於大題小做,永久連接:篤行雜記之Zookeeper SessionTimeOut分析
https://www.ifobnn.com/zookeepersessiontimeout.html