笃行杂记之Zookeeper SessionTimeOut分析

0.前言

本文为笃行日常工作记录,烂笔头系列。

源码前面,了无秘密 — by 侯杰

近期的一个C++项目里使用了Zookeeper做服务发现,期间遇到了SessionTimeOut问题的困扰,明明通过zookeeper c client设置了超时时间,但无效。

请原谅我一开始对zookeeper不熟悉。最终通过分析源码了解到SessionTimeOut最终的确定是一个协商的过程,而不是简单的配置生效。

在这里记录下Session超时时间的有关分析,基于zookeeper 3.4.8

1.zookeeper client SessionTimeOut

项目中使用的是 C client,通过zookeer_init 创建zk session,调用了zookeeper_init其实就开始了建立链接到ZK集群的过程,这里设置的recv_timeout 为客户端所期望的session超时时间,单位为毫秒。

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

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 L1767

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时间实际是经过服务端下发之后的,并不一定是最先设置的。

2.Zookeeper Server SessionTimeOut

2.1协商客户端上报的SessionTimeOut

来看看服务端握手的处理逻辑ZooKeeperServer.java#L876

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.

时间: 2024-09-19 21:22:27

笃行杂记之Zookeeper SessionTimeOut分析的相关文章

Hbase报错:Can&amp;amp;#x27;t get master address from ZooKeeper; znode data == null

问题描述 HADOOP2.2HA模式下安装HBASE0.98.3在配置好文件后进入Hbase报错Can'tgetmasteraddressfromZooKeeper;znodedata==nullHbase-site.xml配置文件:<configuration><property><name>hbase.rootdir</name><value>hdfs://mycluster/hbase</value></property&

【转载】对 Zookeeper 的一些分析

1. zookeeper 不是为高可用性设计的  由于要跨机房容灾,很多系统实际上是需要跨机房部署的.出于性价比的考虑我们通常会让多个机房同时工作,而不会搭建 N 倍的冗余.也就是说单个机房肯定撑不住全流量(你能设想谷歌在全球只剩下一个机房在干活吗).由于 zookeeper 集群只能有一个 master ,因此一旦机房之间连接出现故障,zookeeper master 就只能照顾一个机房,其他机房运行的业务模块由于没有 master 都只能停掉.于是所有流量集中到有 master 的那个机房,

Zookeeper ZAB 协议分析

前言 ZAB 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议.在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性. Atomic broadcast protocol ZAB 是 Zookeeper 原子广播协议的简称,下面我们来讨论协议的内容,注意:理论与实现是有区别的,如果你对协议的理论不感兴趣,可以直接跳过看实现. 问题的提出 Zoo

zookeeper源码分析之leader选举

zookeeper提供顺序一致性.原子性.统一视图.可靠性保证服务zookeeper使用的是zab(atomic broadcast protocol)协议而非paxos协议zookeeper能处理并发地处理多个客户端的写请求,并且以FIFO顺序commit这些写操作,zab采用了一个事务ID来实现事务的全局有序性,在Zab协议的实现时,分为三个阶段:1. Leader Election2. Recovery Phase3. Broadcast Phase 今天就先分析选举算法的源码实现 zoo

Zookeeper之Zookeeper的Client的分析

1)几个重要概念  ZooKeeper:客户端入口 Watcher:客户端注册的callback ZooKeeper.SendThread: IO线程 ZooKeeper.EventThread: 事件处理线程,处理各类消息callback ClientCnxnSocketNIO:继承自ClientCnxnSocket,专门处理IO   2)zookeeper初始化 应用提供watch实例 实例化zookeeper 实例化socket,默认使用ClientCnxnSocketNIO,可通过zoo

Storm-源码分析- Storm中Zookeeper的使用

在backtype.storm.cluster.clj中, 定义了storm对于Zookeeper的使用   ClusterState 首先定义操作Zookeeper集群的interface (defprotocol ClusterState (set-ephemeral-node [this path data]) (delete-node [this path]) (create-sequential [this path data]) (set-data [this path data])

七:zookeeper与paxos的分析

 zookeeper是什么 官方说辞:Zookeeper 分布式服务框架是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务.状态同步服务.集群管理.分布式应用配置项的管理等. 好抽象,我们改变一下方式,先看看它都提供了哪些功能,然后再看看使用它的这些功能能做点什么.   二.            zookeeper提供了什么 简单的说,zookeeper=文件系统+通知机制. 1. 文件系统 Zookeeper维护一个类似文件系

Hibernate源码分析杂记

最近在看hibernate在load entity过程中的操作, 包括为实体类做增强,自动flush,一级缓存,在这里记录一下,慢慢会继续更新.   DefaultLoadEventListener: final PersistenceContext persistenceContext = event.getSession().getPersistenceContext(); StatefulPersistenceContext.proxiesByKey 缓存实体   DefaultLoadE

ZooKeeper源码研究系列(3)单机版服务器介绍

1 系列目录 ZooKeeper源码研究系列(1)源码环境搭建 ZooKeeper源码研究系列(2)客户端创建连接过程分析 ZooKeeper源码研究系列(3)单机版服务器介绍 ZooKeeper源码研究系列(4)集群版服务器介绍 2 单机版服务器启动方式 单机版的服务器启动,使用ZooKeeperServerMain的main函数来启动,参数分为两种: 只有一个参数:表示为一个配置文件地址 有2~4个参数:分别表示端口.dataDir.tickTime.maxClientCnxns 详细介绍见