zookeeper
原始碼分析系列文章:
原創部落格,純手敲,轉載請註明出處,謝謝!
既然我們是要學習原始碼,那麼如何高效地學習原始碼呢?答案就是跟蹤日誌。說到zookeeper
日誌,小編覺得非常的重要,如果沒有日誌檔案,後期的zookeeper
事務處理基本上不能玩。因此,我將zookeeper
日誌放在原始碼分析系列的第二部分,也是在能夠執行zookeeper
的基礎上,進行日誌分析。
一、zookeeper
日誌型別
zookeeper
中有兩類日誌,分別是:
- 1、事務日誌
log
- 2、快照日誌
snapshot
事務日誌 :
顧名思義,就是用於存放事務執行的相關資訊,如zxid
、cxid
等。至於什麼是zookeeper
事務,放到後面的文章中講。
快照日誌 : ``zookeeper
資料節點資料是執行在記憶體中的,當然記憶體儲存這些結點的資料不可能無限大,而且資料節點的內容是動態變化的,因此zookeeper
提供一中獎資料節點持久化的機制,每隔一段時間,zookeeper
會將記憶體中的資料節點DataTree
序列到磁碟中,因此就形成了我們的快照日誌。
在zookeeper
原始碼除錯的過程中,諸如服務端的啟動日誌、客戶端的啟動日誌、請求的相關日誌,由於zookeeper
採用log4j
進行日誌列印,因此log4j
必須在類路徑下查詢log4j.properties
檔案,如果啟動的過程中找不到該檔案,則報錯如下:
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%srcjavamain
,具體如下圖:
所以將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
工具類來實現的。
- 1、在
eclipse
中開該類,然後執行該類的main
方法的同時傳入你想檢視的日誌檔案路徑即可 - 2、採用命令列
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.
複製程式碼
第二種方式:
- 1、在任意目錄下新建一個臨時目錄,隨便命名為
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;
}
複製程式碼
現在原理一目瞭然了,日誌原始碼分析就先到這了,同時你也閱讀完了,非常棒!歡迎評論區留言,多多交流!