摘要:前言本文为笃行日常工作记录,烂笔头系列。最终通过分析源码了解到最终的确定是一个协商的过程,而不是简单的配置生效。根据客户端上报的和服务端自身的。如果上报的小于则设置为如果上报的大于则设置为如果介于两则之间,则以上报的时间为准。
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#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=13.总结
经过源码分析,得出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
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/67253.html
摘要:每个都可以通过其路径唯一标识,同时每个节点还可以存储少量数据。监听机制,监听某个当该发生变化时,会回调该,但是这个是一次性的,下次需要监听时还得再注册一次。 前面的文章中 我用netty实现了一个简单的一对一的RPC 11个类实现简单java rpc 接下来的文章中 我将使用zookeeper作为rpc调用的分布式注册中心 从而实现多对多(多个调用者,多个提供者)的rpc调用,负载均...
摘要:作为整个集群的主节点,负责响应所有对状态变更的请求。选举是最重要的技术之一,也是保障分布式数据一致性的关键所在。这是由于半数以上投票通过决定的。另外需要注意的是,和构成集群的法定人数,也就是说,只有他们才参与新的选举响应的提议。 showImg(https://segmentfault.com/img/remote/1460000016733126); 前言 提到ZooKeeper,相...
摘要:作为整个集群的主节点,负责响应所有对状态变更的请求。选举是最重要的技术之一,也是保障分布式数据一致性的关键所在。这是由于半数以上投票通过决定的。另外需要注意的是,和构成集群的法定人数,也就是说,只有他们才参与新的选举响应的提议。 showImg(https://segmentfault.com/img/remote/1460000016733126); 前言 提到ZooKeeper,相...
摘要:所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的例如著名的项目雅虎的工程师希望给这个项目也取一个动物的名字。 前言 提到ZooKeeper,相信大家都不会陌生。Dubbo,Kafka,Hadoop等等项目里都能看到它的影子。但是你真的了解 ZooKeeper 吗?如...
摘要:的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。具有不可分割性即原语的执行必须是连续的,在执行过程中不允许被中断。 该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb... showImg(https:...
阅读 1334·2021-11-24 09:38
阅读 2063·2021-09-22 15:17
阅读 2262·2021-09-04 16:41
阅读 3371·2019-08-30 15:56
阅读 3494·2019-08-29 17:19
阅读 1923·2019-08-28 18:09
阅读 1228·2019-08-26 13:35
阅读 1691·2019-08-23 17:52