在網際網路設計架構過程中,日誌非同步落庫,儼然已經是高併發環節中不可缺少的一環。為什麼說是高併發環節中不可缺少的呢? 原因在於,如果直接用mq進行日誌落庫的時候,低併發下,生產端生產資料,然後由消費端非同步落庫,是沒有什麼問題的,而且效能也都是異常的好,估計tp99應該都在1ms以內。但是一旦併發增長起來,慢慢的你就發現生產端的tp99一直在增長,從1ms,變為2ms,4ms,直至send timeout。尤其在大促的時候,我司的系統就經歷過這個情況,當時mq的傳送耗時超過200ms,甚至一度有不少timeout產生。
考慮到這種情況在高併發的情況下才出現,所以今天我們就來探索更加可靠的方法來進行非同步日誌落庫,保證所使用的方式不會因為過高的併發而出現介面ops持續下降甚至到不可用的情況。
此種方案由於使用了非同步方式,且由於非同步的discard policy策略,當大量資料過來,緩衝區滿了之後,會拋棄部分資料。此種方案適用於能夠容忍資料丟失的業務場景,不適用於對資料完整有嚴格要求的業務場景。
來看看具體的實現方式:
@resource(name = "messageproducer")
private messageproducer messageproducer;
@override
asyncpushmessage(loggingevent.getmessage()); }
/*** 非同步呼叫jmq輸出日誌
* @param message
*/ private void asyncpushmessage(object message) catch (jmqexception e)
}); }
@override
public boolean requireslayout()
@override
public void close()
}然後在log4j.xml中,為此類進行配置:
最後就可以按照如下的方式進行正常使用了:
注意: 此處需要注意log4j的乙個效能問題。在log4j的conversionpattern中,匹配符最好不要出現 c% l%萬用字元,壓測實踐表明,這兩個萬用字元會導致log4j打日誌的效率降低10倍。
方案一很簡便,且剝離了介面直接依賴mq導致的效能問題。但是無法解決資料丟失的問題(但是我們其實可以在本地搞個策略落盤來不及處理的資料,可以大大的減少資料丟失的機率)。但是很多的業務場景,是需要資料不丟失的,所以這就衍生出我們的另一套方案來。
方案二:增量消費log4j日誌
此種方式,是開啟worker在後台增量消費log4j的日誌資訊,和介面完全脫離。此種方式相比方案一,可以保證資料的不丟失,且可以做到完全不影響介面的ops。但是此種方式,由於是後台worker在後台啟動進行掃瞄,會導致落庫的資料慢一些,比如一分鐘之後才落庫完畢。所以適用於對落庫資料實時性不高的場景。
具體的實現步驟如下:
首先,將需要進行增量消費的日誌統一打到乙個資料夾,以天為單位,每天生成乙個帶時間戳日誌檔案。由於log4j不支援直接帶時間戳的日誌檔案生成,所以這裡需要引入log4j.extras元件,然後配置log4j.xml如下:
之後在**中的申明方式如下:
private static logger businesslogger = logge***ctory.getlogger("file_rolling_logger");最後在需要記錄日誌的地方使用方式如下:
businesslogger.error(jsonutils.tojsonstring(mymessage))這樣就可以將日誌列印到乙個單獨的檔案中,且按照日期,每天生成乙個。
然後,當日誌檔案生成完畢後,我們就可以開啟我們的worker進行增量消費了,這裡的增量消費方式,我們選擇randomaccessfile這個類來進行,由於其獨特的位點讀取方式,可以使得我們非常方便的根據位點的位置來消費增量檔案,從而避免了逐行讀取這種低效率的實現方式。
注意,為每個日誌檔案都單獨建立了乙個位點檔案,裡面儲存了對應的檔案的位點讀取資訊。當worker掃瞄開始的時候,會首先讀取位點檔案裡面的位點資訊,然後找到相應的日誌檔案,從位點資訊位置開始進行消費。這就是整個增量消費worker的核心。具體**實現如下(**太長,做了摺疊):
/**此種方式由於worker掃瞄是每隔一段時間啟動一次進行消費,所以導致資料從產生到入庫,可能經歷時間超過一分鐘以上,但是在一些對資料延遲要求比較高的業務場景,比如庫存扣減,是不能容忍的,所以這裡我們就引申出第三種做法,基於記憶體檔案佇列的非同步日誌消費。* @description: 增量日誌掃瞄worker
* @detail: 此worker主要用來掃瞄增量日誌,日誌本身會在不停的插入中,此worker會不停的掃瞄此日誌來將資料上傳到kafka集群
* @date 2018-04-08 10:30
*/public class limitbuyscanworker
//昨日的檔案未掃瞄完畢
else }
/*** 檢測日誌是否被掃瞄消費完畢,true:消費完畢;false:未消費完畢
* @description 此舉主要防止log4j在零點大促開始的時候,突然的滾動檔案造成的部分增量日誌不會被消費的問題
* @param logfilepath
* @param offsetfilepath
*/ private boolean checkifconsumeok(string logfilepath, string offsetfilepath)
return false;
} catch (filenotfoundexception e) catch (ioexception e) }
/*** 掃瞄並消費增量日誌
* @param logfilepath
* @param offsetfilepath
*/ private void scanandconsumelog(string logfilepath, string offsetfilepath)
//讀取@scan_step行
for (long i = currentoffset; i < currentoffset + scan_step; i++) }
//讀取@scan_step行之後的位點
logger.error("讀取" + scan_step + "行之後位點==>" + randomaccessfile.getfilepointer());
//如果update不成功,可以不處理,後面掃瞄進來重新過一遍即可
updateoffset(randomaccessfile.getfilepointer(), offsetfilepath);
logger.error("檔案總長==>" + randomaccessfile.length());
} catch (filenotfoundexception e) catch (ioexception e) }
/*** 校驗位點
* 不存在則建立並賦值為0
* 已存在則更新位點
* @param offsetfilepath
* @return
* @throws ioexception
*/ private long checkoffset(string offsetfilepath) throws ioexception
//如果位點檔案存在,則返回位點檔案內容
else
string filteredoffset = stringbuilder.tostring().trim();
if (stringutil.isnotblank(filteredoffset)) else
} }/**
* 更新位點資訊
* @param offset
* @param offsetfilepath
*/ private void updateoffset(long offset, string offsetfilepath) throws ioexception
}
方案三:基於記憶體檔案佇列的非同步日誌消費
由於方案一和方案二都嚴重依賴log4j,且方案本身都存在著要麼丟資料,要麼入庫時間長的缺點,所以都並不是那麼盡如人意。但是本方案的做法,既解決了資料丟失的問題,又解決了資料入庫時間被拉長的尷尬,所以是終極解決之道。而且在大**過程中,此種方式經歷了實戰檢驗,可以大面積的推廣使用。
本群提供免費的學習指導 架構資料 以及免費的解答
不懂得問題都可以在本群提出來 之後還會有直播平台和講師直接交流噢
哦對了,喜歡就別忘了關注一下哦~
你所不知道的 const
const 常量是不可修改的,也就是說only read,例如 const int nbuffsize 512 nbuffsize 0 error就是因為const 常量不能修改,所以定義時必須初始化預設在全域性作用域中定義的非const變數可以在整個程式中訪問,例如 int ncounter ex...
你所不知道的background
今天要說說css中background這個屬性裡面的大學問。在乙個宣告中設定所有的背景屬性 body 看到這串 你怕了嗎?知道他們都代表啥意思嘛?不要捉急,來看展開式。展開式 background color設定元素的背景顏色,不能設定到外邊距,可以繼承父級的背景顏色,預設為透明。backgroun...
overflow hidden 你所不知道的事
overflow hidden 你所不知道的事 overflow hidden這個css樣式是大家常用到的css樣式,但是大多數人對這個樣式的理解僅僅侷限於隱藏溢位,而對於清除浮動這個含義不是很了解。這是乙個常用的div寫法,下面我們來書寫樣式。大家可以在dmx中自己做試驗 wai nei 可以看到...