篤行雜記之Zookeeper SessionTimeOut分析

0.前言

本文爲篤行平常工做記錄,爛筆頭系列。html

源碼前面,了無祕密 — by 侯傑java

近期的一個C++項目裏使用了Zookeeper作服務發現,期間遇到了SessionTimeOut問題的困擾,明明經過zookeeper c client設置了超時時間,但無效。c++

請原諒我一開始對zookeeper不熟悉。最終經過分析源碼瞭解到SessionTimeOut最終的肯定是一個協商的過程,而不是簡單的配置生效。git

在這裏記錄下Session超時時間的有關分析,基於zookeeper 3.4.8github

1.zookeeper client SessionTimeOut

項目中使用的是 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#L1485c#

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

2.Zookeeper Server SessionTimeOut

2.1協商客戶端上報的SessionTimeOut

來看看服務端握手的處理邏輯ZooKeeperServer.java#L876ide

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必定要在服務端設置的上下界之間,若是越過邊界,則以邊界爲準。

2.2 服務端MinSessionTimeOut和MaxSessionTimeOut的肯定

繼續看ZooKeeperServer.java#L104ZooKeeperServer.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

3.總結

通過源碼分析,得出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
相關文章
相關標籤/搜索