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; } 複製代碼
如今原理一目瞭然了,日誌源碼分析就先到這了,同時你也閱讀完了,很是棒!歡迎評論區留言,多多交流!