Zookeeper原始碼分析(二) —– zookeeper日誌

擁抱心中的夢想發表於2019-03-02

zookeeper原始碼分析系列文章:

原創部落格,純手敲,轉載請註明出處,謝謝!

既然我們是要學習原始碼,那麼如何高效地學習原始碼呢?答案就是跟蹤日誌。說到zookeeper日誌,小編覺得非常的重要,如果沒有日誌檔案,後期的zookeeper事務處理基本上不能玩。因此,我將zookeeper日誌放在原始碼分析系列的第二部分,也是在能夠執行zookeeper的基礎上,進行日誌分析。

一、zookeeper日誌型別

zookeeper中有兩類日誌,分別是:

  • 1、事務日誌log
  • 2、快照日誌snapshot

事務日誌 : 顧名思義,就是用於存放事務執行的相關資訊,如zxidcxid等。至於什麼是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,具體如下圖:

Zookeeper原始碼分析(二) —– zookeeper日誌

所以將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原始碼分析(二) —– zookeeper日誌

既然是二進位制日誌檔案,那麼我們直接開啟該檔案肯定是亂碼嘛!怎麼辦呢?下面提供兩種方法,這兩種方法都是基於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原始碼分析(二) —– zookeeper日誌

然後就可以在控制檯輸出日誌了,輸出樣例如下:

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.jarzookeeper-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日誌清理機制

zookeeper是將日誌以檔案的形式存放在磁碟中,久而久之,磁碟的檔案就越來越多,zookeeper提供了一種定期清理日誌和快照檔案的機制。

QuorumPeerMain.javazookeeper的啟動類,在zookeeper啟動之前會建立一個DatadirCleanupManager物件進行資料清理任務,DatadirCleanupManager會根據你配置檔案的配置決定是否清理以及每隔多久進行清理,其底層原理採用JDKTimer定時器實現,下面將對其實現機制從原始碼的角度進行分析:

先看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.snapRetainCountautopurge.snapRetainCountautopurge.purgeInterval就是設定多少小時清理一次。而autopurge.snapRetainCount是設定保留多少個快照檔案snapshot,之前的多有都刪除。

有一點效能問題就是,一般zookeeper是執行在叢集中,業務會比較繁忙,如果每隔多久去清理勢必會影響效能,我們會想能否有一種在叢集不繁忙的時候去執行清理操作,比如在每晚的12點。但是很遺憾,zookeeper並沒有提供相應的實現,zookeeper採用Timer的方式去實現,而不是像quartz,Spring一樣提供cron表示式配置。但注意,並不是說Timer不能實現指定時間執行,而是說zookeeper沒有實現而已。因為quartz,Spring底層還是使用TimerExecutors去實現的嘛!

再來看看DatadirCleanupManagerstart()方法:

 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;
    }
複製程式碼

現在原理一目瞭然了,日誌原始碼分析就先到這了,同時你也閱讀完了,非常棒!歡迎評論區留言,多多交流!

相關文章