文章目錄
什麼情況下開啓配置/腳本熱更新?
當monitorInterval屬性的值不爲null,並且配置文件是存在的時候,Log4J2也有一套機制來實現對配置文件的熱更新,簡單說也就是當文件被改變的時候,Log4j2會動態的加載最新的配置。
以XmlConfiguration爲例:
//省略部分解析配置的代碼
if ("monitorInterval".equalsIgnoreCase(key)) {
final int intervalSeconds = Integer.parseInt(value);
if (intervalSeconds > 0) {
//獲取WatchManager, 並設置配置監控間隔
getWatchManager().setIntervalSeconds(intervalSeconds);
//如果當前配置文件不爲null,則創建配置文件觀察者
if (configFile != null) {
final FileWatcher watcher = new ConfiguratonFileWatcher(this, listeners);
//添加文件監控
getWatchManager().watchFile(configFile, watcher);
}
}
}
關鍵類與接口
WatchManager
-
作用:
- 負責管理要監控的文件與文件監控器
- 定時掃描要監控的文件,並通過FileMonitor判斷文件是否被修改
-
監控管理
private final ConcurrentMap<File, FileMonitor> watchers = new ConcurrentHashMap<>();
public void watchFile(final File file, final FileWatcher watcher) {
watchers.put(file, new FileMonitor(file.lastModified(), watcher));
}
- 定時監控
public void start() {
//設置狀態爲已啓動
super.start();
//intervalSeconds即monitorInterval的值
if (intervalSeconds > 0) {
//啓動定時器
future = scheduler.scheduleWithFixedDelay(new WatchRunnable(), intervalSeconds, intervalSeconds,
TimeUnit.SECONDS);
}
}
private class WatchRunnable implements Runnable {
@Override
public void run() {
//遍歷要監控的文件列表
for (final Map.Entry<File, FileMonitor> entry : watchers.entrySet()) {
final File file = entry.getKey();
final FileMonitor fileMonitor = entry.getValue();
//獲取文件最新的更改時間
final long lastModfied = file.lastModified();
//判斷文件是否修改
if (fileModified(fileMonitor, lastModfied)) {
logger.info("File {} was modified on {}, previous modification was {}", file, lastModfied, fileMonitor.lastModifiedMillis);
fileMonitor.lastModifiedMillis = lastModfied; //通過fileWatcher文件已被需改
fileMonitor.fileWatcher.fileModified(file);
}
}
}
//文件最近修改時間如果大於上次修改時間,則認定文件被修改
private boolean fileModified(final FileMonitor fileMonitor, final long lastModifiedMillis) {
return lastModifiedMillis != fileMonitor.lastModifiedMillis;
}
}
FileMonitor
- 作用:存儲文件上次修改的時間,與文件對應的FileWatcher。當文件被判斷修改的時候,通知FileWatcher的實現類進行相應的操作。
- 代碼
private class FileMonitor {
private final FileWatcher fileWatcher;
private long lastModifiedMillis;
public FileMonitor(final long lastModifiedMillis, final FileWatcher fileWatcher) {
this.fileWatcher = fileWatcher;
this.lastModifiedMillis = lastModifiedMillis;
}
@Override
public String toString() {
return "FileMonitor [fileWatcher=" + fileWatcher + ", lastModifiedMillis=" + lastModifiedMillis + "]";
}
}
FileWatcher(接口)
- 作用:文件被修改之後, 間接或者直接的執行被修改之後的動作, 譬如重新解析配置文件,然後更新配置。
實現類0:ScriptManager
- 作用:腳本管理器
- 我們看看fileModified的方法實現。
@Override
public void fileModified(final File file) {
//根據文件名獲取腳本的執行器
final ScriptRunner runner = scriptRunners.get(file.toString());
if (runner == null) {
logger.info("{} is not a running script");
return;
}
//獲取執行引擎
final ScriptEngine engine = runner.getScriptEngine();
//獲取腳本信息,如語言,內容,名字
final AbstractScript script = runner.getScript();
//根據是否有KEY_THREADING參數會有不同的運行機制
//將更新的腳本放入到Map中,等待被執行
if (engine.getFactory().getParameter(KEY_THREADING) == null) {
scriptRunners.put(script.getName(), new ThreadLocalScriptRunner(script));
} else {
scriptRunners.put(script.getName(), new MainScriptRunner(engine, script));
}
}
ps: 腳本的作用及用法可以自行看看官方解釋 - scripts 部分。
實現類1:ConfiguratonFileWatcher
- 作用:log的配置文件觀察者。
- 同樣,我們來看看其fileModified方法:
public void fileModified(final File file) {
//遍歷watcher中的所有監聽器,並啓動相應的線程來通知監聽器執行動作,其實這裏可以理解成有ConfiguratonFileWatcher是對多個FileWtacher的一個包裝
for (final ConfigurationListener configurationListener : configurationListeners) {
final Thread thread = threadFactory.newThread(new ReconfigurationRunnable(configurationListener, reconfigurable));
thread.start();
}
}
- 上面線程要運行的內容,其實就是調用listener的onChange方法
ReconfigurationRunnable
private static class ReconfigurationRunnable implements Runnable {
private final ConfigurationListener configurationListener;
private final Reconfigurable reconfigurable;
public ReconfigurationRunnable(final ConfigurationListener configurationListener, final Reconfigurable reconfigurable) {
this.configurationListener = configurationListener;
this.reconfigurable = reconfigurable;
}
@Override
public void run() {
configurationListener.onChange(reconfigurable);
}
}
- ConfigurationListener:目前在Log4J2中只有一個實現,就是LoggerContext,我們來看看onChange方法:
public synchronized void onChange(final Reconfigurable reconfigurable) {
LOGGER.debug("Reconfiguration started for context {} ({})", contextName, this);
//通過reconfigurable獲取最新的配置
final Configuration newConfig = reconfigurable.reconfigure();
if (newConfig != null) {
//更新LoggerContext的配置,也就是整個日誌系統的配置
setConfiguration(newConfig);
LOGGER.debug("Reconfiguration completed for {} ({})", contextName, this);
} else {
LOGGER.debug("Reconfiguration failed for {} ({})", contextName, this);
}
}
- Reconfigurable: 實現對配置文件的重新配置
- 實現類有以下5種,其實也就是配置文件的解析類
- 以XmlConfiguration爲例,我們看看看它是怎麼實現Reconfigurable接口的
- 實現類有以下5種,其實也就是配置文件的解析類
public Configuration reconfigure() {
try {
//將配置源文件轉化爲輸入流
final ConfigurationSource source = getConfigurationSource().resetInputStream();
if (source == null) {
return null;
}
//重新生成XmlConfiguration
final XmlConfiguration config = new XmlConfiguration(getLoggerContext(), source);
//判斷新生成的配置信息是否合法:簡單驗證一下是否有root節點
return config.rootElement == null ? null : config;
} catch (final IOException ex) {
LOGGER.error("Cannot locate file {}", getConfigurationSource(), ex);
}
return null;
}
小結
學習這套監控機制還是挺受益的,可以聯想到其他的很多監控場景,譬如說服務掛掉之後自動重啓等等,相似的套路。區別在於,要監控的是進程的狀態(目標),當進程不存在的時候(事件),就重新執行進程的啓動命令(事件對應的動作)。