zookeeper
源码分析系列文章:html
原创博客,纯手敲,转载请注明出处,谢谢!java
既然咱们是要学习源码,那么如何高效地学习源代码呢?答案就是跟踪日志。说到zookeeper
日志,小编以为很是的重要,若是没有日志文件,后期的zookeeper
事务处理基本上不能玩。所以,我将zookeeper
日志放在源码分析系列的第二部分,也是在可以运行zookeeper
的基础上,进行日志分析。apache
zookeeper
日志类型zookeeper
中有两类日志,分别是:api
log
snapshot
事务日志 :
顾名思义,就是用于存放事务执行的相关信息,如zxid
、cxid
等。至于什么是zookeeper
事务,放到后面的文章中讲。bash
快照日志 : ``zookeeper
数据节点数据是运行在内存中的,固然内存保存这些结点的数据不可能无限大,并且数据节点的内容是动态变化的,所以zookeeper
提供一中奖数据节点持久化的机制,每隔一段时间,zookeeper
会将内存中的数据节点DataTree
序列到磁盘中,所以就造成了咱们的快照日志。session
在zookeeper
源码调试的过程当中,诸如服务端的启动日志、客户端的启动日志、请求的相关日志,因为zookeeper
采用log4j
进行日志打印,所以log4j
必须在类路径下查找log4j.properties
文件,若是启动的过程当中找不到该文件,则报错以下:app
log4j:WARN No appenders could be found for logger (org.apache.log4j.jmx.HierarchyDynamicMBean).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
复制代码
所以,必须将log4j.properties
放到类路径下,那么对于可恶的ant
工程,类路径在哪里呢?我也不清楚,因而我直接在eclipse
右键工程Build Path
->Configure build path
找到类路径为%baseDir%\src\java\main
,具体以下图:eclipse
因此将conf
目录下的log4j.properties
拷贝一份至上图的目录中便可!工具
zookeeper
日志可视化上面说到zookeeper
中有两种日志类型,但很遗憾,上面的日志你都没法直接看到,由于都是采用二进制进行保存的。查看zookeeper
源代码也能够知道,zookeeper
日志输出体如今FileTxnLog.java
文件的append()
方法中,具体以下:源码分析
// 写日志文件,采用log. + 哈希值的命名形式保存文件
logFileWrite = new File(logDir, ("log." + Long.toHexString(hdr.getZxid())));
// 文件输出流
fos = new FileOutputStream(logFileWrite);
// 采用BufferedOutputStream包裹fos成缓冲输出流
logStream=new BufferedOutputStream(fos);
// 这是重点,这里采用org.apache.jute.BinaryOutputArchive二进制构件对logStream进行包裹,输出二进制数据
oa = BinaryOutputArchive.getArchive(logStream);
复制代码
对应的文件系统的文件以下图:
既然是二进制日志文件,那么咱们直接打开该文件确定是乱码嘛!怎么办呢?下面提供两种方法,这两种方法都是基于zookeeper
提供的LogFormatter.java
工具类来实现的。
eclipse
中开该类,而后运行该类的main
方法的同时传入你想查看的日志文件路径便可java -classpath xxx.jar org.apache.zookeeper.server.LogFormatter logFilePath
的形式进行查看第一种方式:
这里假设个人日志文件存放路径为E:\\resources\\zookeeper-3.4.11\\conf\\log\\version-2\\log.1
,当我在eclipse
中运行main
方法时,设置传入的参数便可,以下图:
而后就能够在控制台输出日志了,输出样例以下:
ZooKeeper Transactional Log File with dbid 0 txnlog format version 2
18-4-30 下午08时39分23秒 session 0x100022b44190000 cxid 0x0 zxid 0x1 createSession 30000
18-4-30 下午08时39分55秒 session 0x100022b44190000 cxid 0x0 zxid 0x2 closeSession null
EOF reached after 2 txns.
复制代码
第二种方式:
logSee
,将slf4j-api-1.6.1.jar
和zookeeper-3.4.11.jar
两个文件放到该目录下,而后打开命令行,执行java -classpath .;slf4j-api-1.6.1.jar;zookeeper-3.4.11.jar org.apache.zookeeper.server.LogFormatter E:\\resources\\zookeeper-3.4.11\\conf\\log\\version-2\\log.1
其中两个jar
包之间采用分号;
分隔而不是冒号,org.apache.zookeeper.server.LogFormatter
表示LogFormatter.java
的全路径命名(即包全名+类名
),E:\\resources\\zookeeper-3.4.11\\conf\\log\\version-2\\log.1
表示你想查看的日志文件。
输出以下:
zookeeper
日志清理机制zookeeper
是将日志以文件的形式存放在磁盘中,长此以往,磁盘的文件就愈来愈多,zookeeper
提供了一种按期清理日志和快照文件的机制。
QuorumPeerMain.java
是zookeeper
的启动类,在zookeeper
启动以前会建立一个DatadirCleanupManager
对象进行数据清理任务,DatadirCleanupManager
会根据你配置文件的配置决定是否清理以及每隔多久进行清理,其底层原理采用JDK
的Timer
定时器实现,下面将对其实现机制从源码的角度进行分析:
先看QuorumPeerMain
类,在其initializeAndRun()
方法执行时会建立一个DatadirCleanupManager
对象,并将zoo.cfg
配置文件的相关配置传递给该对象,源码以下:
protected void initializeAndRun(String[] args)
throws ConfigException, IOException
{
QuorumPeerConfig config = new QuorumPeerConfig();
if (args.length == 1) {
config.parse(args[0]);
}
// 启动数据目录清理定时任务,传递的参数有数据目录DataDir,日志目录LogDir,以及快照保留数量count,默认>3,最后一个是每一个多长时间进行清理
DatadirCleanupManager purgeMgr = new DatadirCleanupManager(config
.getDataDir(), config.getDataLogDir(), config
.getSnapRetainCount(), config.getPurgeInterval());
purgeMgr.start();
if (args.length == 1 && config.servers.size() > 0) {
runFromConfig(config);
} else {
LOG.warn("Either no config or no quorum defined in config, running "
+ " in standalone mode");
// there is only server in the quorum -- run as standalone
ZooKeeperServerMain.main(args);
}
}
复制代码
你能够在配置文件zoo.cfg
文件中增长两个配置,分别是autopurge.snapRetainCount
和autopurge.snapRetainCount
。autopurge.purgeInterval
就是设置多少小时清理一次。而autopurge.snapRetainCount
是设置保留多少个快照文件snapshot
,以前的多有都删除。
有一点性能问题就是,通常zookeeper
是运行在集群中,业务会比较繁忙,若是每隔多久去清理势必会影响性能,咱们会想可否有一种在集群不繁忙的时候去执行清理操做,好比在每晚的12点。可是很遗憾,zookeeper
并无提供相应的实现,zookeeper
采用Timer
的方式去实现,而不是像quartz
,Spring
同样提供cron
表达式配置。但注意,并非说Timer
不能实现指定时间执行,而是说zookeeper
没有实现而已。由于quartz
,Spring
底层仍是使用Timer
和Executors
去实现的嘛!
再来看看DatadirCleanupManager
的start()
方法:
public void start() {
if (PurgeTaskStatus.STARTED == purgeTaskStatus) {
LOG.warn("Purge task is already running.");
return;
}
// Don't schedule the purge task with zero or negative purge interval. if (purgeInterval <= 0) { LOG.info("Purge task is not scheduled."); return; } // 建立TIMER timer = new Timer("PurgeTask", true); // 建立定时任务 TimerTask task = new PurgeTask(dataLogDir, snapDir, snapRetainCount); // 每隔多少小时执行 timer.scheduleAtFixedRate(task, 0, TimeUnit.HOURS.toMillis(purgeInterval)); purgeTaskStatus = PurgeTaskStatus.STARTED; } 复制代码
如今原理一目了然了,日志源码分析就先到这了,同时你也阅读完了,很是棒!欢迎评论区留言,多多交流!