配置自动清理日志
从3.4.0开始,会自动清理日志了,所以这个通常不用配置。配置项是autopurge.snapRetainCount
和 autopurge.purgeInterval
参数。保留的snapshop的数量,默认是3个,最小也是3.
autopurge.snapRetainCount=3
autopurge.purgeInterval=1
配置zookeeper.out的位置及log4j滚动日志输出
发现线上的bin/zookeeper.out 居然有6G大小。看了下zkServer.sh的代码,这个zookeeper.out实际上是nohup的输出。
而nohup的输出实际上是stdout,stderr的输出,所以还是zookeepe本身的日志配置的问题。
研究了下bin/zkServer.sh和conf/log4j.properties,发现zookeeper其实是有日志相关的输出的配置,只要定义相关的变量就可以了。
主要是ZOO_LOG_DIR
和ZOO_LOG4J_PROP
这两个环境变量,zkServer.sh里的:
if [ ! -w "$ZOO_LOG_DIR" ] ; then
mkdir -p "$ZOO_LOG_DIR"
fi
_ZOO_DAEMON_OUT="$ZOO_LOG_DIR/zookeeper.out"
nohup $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
-cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null &
log4j.properties里的:
#
# Add ROLLINGFILE to rootLogger to get log file output
# Log DEBUG level and above messages to a log file
log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLINGFILE.Threshold=${zookeeper.log.threshold}
log4j.appender.ROLLINGFILE.File=${zookeeper.log.dir}/${zookeeper.log.file}
而zkServer.sh会加载zkEnv.sh。
因此,其实修改下bin/zkEnv.sh就可以了:
if [ "x${ZOO_LOG_DIR}" = "x" ]
then
ZOO_LOG_DIR="$ZOOBINDIR/../logs"
fi
if [ "x${ZOO_LOG4J_PROP}" = "x" ]
then
ZOO_LOG4J_PROP="INFO,ROLLINGFILE"
fi
还可以修改下conf/log4j.properties
,设置滚动日志最多为10个:
# Max log file size of 10MB
log4j.appender.ROLLINGFILE.MaxFileSize=10MB
# uncomment the next line to limit number of backup files
log4j.appender.ROLLINGFILE.MaxBackupIndex=10
too many connections from 错误
这个错误是因为同一个IP的zookeeper socket 连接数大于60了。zookeeper server默认限制每个IP最多60个连接。可以在配置文件中修改:maxClientCnxns=150
Client和ZK集群的连接和Session的建立过程
ZooKeeper对象一旦创建,就会启动一个线程(ClientCnxn)去连接ZK集群。ZooKeeper内部维护了一个Client端状态。
public enum States {
CONNECTING, ASSOCIATING, CONNECTED, CLOSED, AUTH_FAILED;
//…
}
第一次连接ZK集群时,首先将状态置为CONNECTING
,然后挨个尝试连接serverlist中的每一台Server。Serverlist在初始化时,顺序已经被随机打乱:Collections.shuffle(serverAddrsList)
,这样可以避免多个client以同样的顺序重连server。重连的间隔毫秒数是0-1000之间的一个随机数。
一旦连接上一台server,首先发送一个ConnectRequest包,将ZooKeeper构造函数传入的sessionTimeout数值发动给Server。ZooKeeper Server有两个配置项:
minSessionTimeout 单位毫秒。默认2倍tickTime
maxSessionTimeout 单位毫秒。默认20倍tickTime
如果客户端发来的sessionTimeout超过min-max这个范围,server会自动截取为min或max,然后为这个Client新建一个Session对象。Session对象包含sessionId、timeout、tickTime三个属性。其中sessionId是Server端维护的一个原子自增long型(8字节)整数;启动时Leader将其初始化为1个字节的leader Server Id+当前时间的后5个字节+2个字节的0;这个可以保证在leader切换中,sessionId的唯一性(只要leader两次切换为同一个Server的时间间隔中session建立数不超过( 2的16次方)*间隔毫秒数。。。不可能达到的数值)。
ZK Server端维护如下3个Map结构,Session创建后相关数据分别放入这三个Map中:
Map<Long[sessionId], Session> sessionsById
Map<Long[sessionId], Integer> sessionsWithTimeout
Map<Long[tickTime], SessionSet> sessionSets
其中sessionsById简单用来存放Session对象及校验sessionId是否过期。sessionsWithTimeout用来维护session的持久化:数据会写入snapshot,在Server重启时会从snapshot恢复到sessionsWithTimeout,从而能够维持跨重启的session状态。
Session对象的tickTime属性表示session的过期时间。sessionSets这个Map会以过期时间为key,将所有过期时间相同的session收集为一个集合。Server每次接到Client的一个请求或者心跳时,会根据当前时间和其sessionTimeout重新计算过期时间并更新Session对象和sessionSets。计算出的过期时间点会向上取整为ZKServer的属性tickTime的整数倍。Server启动时会启动一个独立的线程负责将大于当前时间的所有tickTime对应的Session全部清除关闭。
Leader收到连接请求后,会发起一个createSession的Proposal,如果表决成功,最终所有的Server都会在其内存中建立同样的Session,并作同样的过期管理。等表决通过后,与客户端建立连接的Server为这个session生成一个password,连同sessionId,sessionTimeOut一起返回给客户端(ConnectResponse)。客户端如果需要重连Server,可以新建一个ZooKeeper对象,将上一个成功连接的ZooKeeper 对象的sessionId和password传给Server
ZooKeeper zk = new ZooKeeper(serverList, sessionTimeout, watcher, sessionId,passwd);
ZKServer会根据sessionId和password为同一个client恢复session,如果还没有过期的话。
Server生成password的算法比较有意思:new Random(sessionId ^ superSecret).nextBytes(byte[] passwd)
superSecret是一个固定的常量。Server不保存password,每次在返回client的ConnectRequest应答时计算生成。在客户端重连时再重新计算,与传入的password作比较。因为Random相同的seed随机生成的序列是完全相同的!
Client发送完ConnectRequest包,会紧接着发送authInfo包(OpCode.auth)和setWatches 包OpCode.setWatches;authInfo列表由ZooKeeper的addAuthInfo()方法添加,用来进行自定义的认证和授权。
最后当zookeeper.disableAutoWatchReset为false时,若建立连接时ZooKeeper注册的Watcher不为空,那么会通过setWatches告诉ZKServer重新注册这些Watcher。这个用来在Client自动切换ZKServer或重练时,尚未触发的Watcher能够带到新的Server上
以上是连接初始化的时候做的事情。