基于Ignite+Lucene+Log4j2的分布式統一日志查詢最佳實踐
1.背景
應用開發時的常規做法,是調用日志系統的API進行日志的記錄,日志的具體記錄方式,通過日志系統實現庫對應的配置文件進行配置,比如使用log4j2的話,可能就是 log4j2.xml 文件,日志通常是記錄到文件中的,如果要查看日志,就得登錄該服務器進行實地查看。這樣如果應用以集群的方式進行部署,然后又不知道問題出現在哪臺服務器,這時就需要登錄每一臺服務器,這給系統的開發、測試和運維帶來了很多的麻煩。
但是和時下的互聯網公司不同,企業級應用通常不需要對日志進行分析,通過正常的數據存儲,比如數據庫,就可以獲得絕大部分想要的數據。
2.目標
問題是比較明確的,需求也比較清晰,就是希望能設計一套解決方案解決這個問題,大致整理一下,應該包括如下技術點:
- 對應用透明 :對應用的開發者而言,還是像原來那樣記錄日志,不需要關注日志是如何記錄、以什么形式保存在哪里的;
- 有相當的靈活性 :希望是可以靈活配置的,除了可以按照關鍵字查詢外,最好還可以靈活地自定義其他的維度,方便根據具體的業務場景,進行有針對性的查詢,比如按照時間段進行查詢,按照具體的業務指標進行查詢等;
- 有統一的查詢界面 :通過一個統一的界面,能夠查詢到整個集群范圍內的日志,比如輸入某個關鍵詞,無需關注日志保存在哪臺服務器上;
- 有較高的性能 :保證有較高的查詢速度;
- 低資源占用 :占用較少的系統資源,包括CPU、內存,甚至不需要單獨的服務器進行部署;
- 部署簡單 :不需要復雜的配置,部署簡單。
3.架構方案
在綜合考慮了前述背景、約束以及設計目標,綜合考慮了現有開源社區的解決方案之后,我們決定采用 Lucene + Ignite + Log4j2 的技術方案,整體架構圖如下:
大致描述下設計思路:
- 技術選型 :整個技術方案,涉及了三個流行的開源庫,分別是: Lucene 、 Ignite 、 Log4j2 ,我們使用的版本分別是 5.5.4 、 1.9.0 和 2.7 ,他們起的作用如下:
- Lucene :用于日志的存儲、索引和查詢,它為開發者提供了簡單明了的API,使用非常方便, Lucene 在這個技術方案中,是實現目標的基礎;
- Ignite :在這個技術方案中使用了 Ignite 的服務網格和計算網格技術,這里, Ignite 的服務用于對查詢系統暴露接口, Ignite 中基于 MapReduce 范式的嵌入式分布式計算用于任務的分發,查詢結果的匯總。 Ignite 在本技術方案中并非必需,但是使用 Ignite 的簡單API,可以大幅降低本技術方案的實現難度和開發工作量,也使本技術方案變得優雅;
- Log4j2 :本技術方案中的另一個關鍵點,就是對 Log4j2 進行了擴展,自定義了一套基于 Lucene 的 Appender 實現以及對應的 log4j2.xml 配置方案,它解決了對應用透明的問題,還是整個方案具有靈活性的關鍵,就是具體對那些屬性建立索引、輸出那些信息以及輸出信息的格式、數據的類型都是在 log4j2.xml 中進行配置的。
- 執行流程 :
- log4j2.xml配置 :首先要進行 log4j2.xml 文件的配置,配置方式后面會介紹;
- 日志的記錄 : log4j2.xml 配置好之后,對于應用來說,就按照正常的日志記錄方式寫代碼即可,沒有特別的要求,唯一特別要注意的是,如果要針對特別的業務指標建立索引,以名為 id 的變量為例,那么需要在代碼中對相關的上下文變量進行賦值,大體為: ThreadContext.put("id", id); ;
- Ignite服務的調用 :用戶通過查詢界面輸入關鍵詞之后,后臺會通過 Ignite 服務發布出來的接口調用遠程業務系統的服務實現進行實際日志的查詢。這里 Ignite 服務的部署,可以是集群單例,也可以是每節點單例,如果是每節點單例,調用時 Ignite 會以負載平衡的方式隨機地選擇一個節點;
- 任務的分發 :服務的實現會調用 Ignite 計算網格的任務(Task),任務會按照集群節點的數量生成對應數量的作業(Job),如果作業數和節點數相等, Ignite 會將這些作業平均分到每一個節點上去執行;
- 作業的執行 :作業的執行過程就是調用 Lucene 的API進行實際的查詢了,這里沒什么特別的,如果只是通過關鍵字進行查詢,會比較簡單,如果想通過多維度進行精確地查詢,API調用方面會復雜一點,但是都不是難事,不會的可以百度;
- 任務的匯總 :各個節點查詢的結果集,需要匯總后返回給調用端; </ul> </li> </ul>
4.關鍵技術點
4.1.Ignite
Apache Ignite內存數據組織平臺是一個高性能、集成化、混合式的企業級分布式架構解決方案,核心價值在于可以幫助我們實現分布式架構透明化,開發人員根本不知道分布式技術的存在,可以使分布式緩存、計算、存儲等一系列功能嵌入應用內部,和應用的生命周期一致,大幅降低了分布式應用開發、調試、測試、部署的難度和復雜度。
4.2.Ignite服務網格
Ignite 服務網格以一種優雅的方式實現了分布式RPC,定義一個服務非常簡單:
下面通過一個簡單的示例演示下 Ignite 服務的定義、實現、部署和調用:
4.2.1.服務定義
public interface MyCounterService { int get() throws CacheException; }
4.2.2.服務實現
public class MyCounterServiceImpl implements Service, MyCounterService { @Override public int get() { return 0; } }
4.2.3.服務部署
ClusterGroup cacheGrp = ignite.cluster().forCache("myCounterService"); IgniteServices svcs = ignite.services(cacheGrp); svcs.deployNodeSingleton("myCounterService", new MyCounterServiceImpl());
4.2.4.服務調用
MyCounterService cntrSvc = ignite.services(). serviceProxy("myCounterService", MyCounterService.class, /*not-sticky*/false); System.out.println("value : " + cntrSvc.get());
是不是很簡單?
4.3.Ignite計算網格
Ignite的分布式計算是通過 IgniteCompute 接口提供的,它提供了在集群節點或者一個集群組中運行很多種類型計算的方法,這些方法可以以一個分布式的形式執行任務或者閉包。
本方案中采用的是 ComputeTask 方式,它是 Ignite 對于簡化內存內 MapReduce 的抽象。 ComputeTask 定義了要在集群內執行的作業以及這些作業到節點的映射,還定義了如何處理作業的返回值(Reduce)。所有的 IgniteCompute.execute(...) 方法都會在集群上執行給定的任務,應用只需要實現 ComputeTask 接口的 map(...) 和 reduce(...) 方法即可,這幾個方法的詳細描述不在本文討論的范圍內。
下面是一個 ComputeTask 的簡單示例:
IgniteCompute compute = ignite.compute(); int cnt = compute.execute(CharacterCountTask.class, "Hello Grid Enabled World!"); System.out.println(">>> Total number of characters in the phrase is '" + cnt + "'."); private static class CharacterCountTask extends ComputeTaskSplitAdapter<String, Integer> { @Override public List<ClusterNode> split(int gridSize, String arg) { String[] words = arg.split(" "); List<ComputeJob> jobs = new ArrayList<>(words.length); for (final String word : arg.split(" ")) { jobs.add(new ComputeJobAdapter() { @Override public Object execute() { System.out.println(">>> Printing '" + word + "' on from compute job."); return word.length(); } }); } return jobs; } @Override public Integer reduce(List<ComputeJobResult> results) { int sum = 0; for (ComputeJobResult res : results) sum += res.<Integer>getData(); return sum; } }
通過這樣一個簡單的類,就實現了夢寐以求的分布式計算!
4.4.自定義的Log4j LuceneAppender擴展
本方案的自定義log4j LuceneAppender 擴展,是做到應用無感知,高靈活性和高性能的關鍵, LuceneAppender 的具體實現,不在本文的討論范圍內,但是要介紹下本方案的配置方式,大體配置方式如下(簡略):
<Lucene name="luceneAppender" ignoreExceptions="true" target="target/lucene/index" expiryTime="1296000"> <IndexField name="logId" pattern="$${ctx:logId}" /> <IndexField name="time" pattern="%d{UNIX_MILLIS}" type = "LongField"/> <IndexField name="level" pattern="%-5level" /> <IndexField name="content" pattern="%class{36} %L %M - %msg%xEx%n" /> </Lucene>
其中: target 屬性表示索引文件的位置, expiryTime 屬性表示索引過期時間(秒), IndexField 標簽表示具體的索引項,其中 name 屬性是字段名, pattern 屬性同 log4j 自身的 pattern 屬性, type 屬性表示字段類型,目前支持 LongField , TextField 以及 StringField 。
4.5.Lucene分析器
LuceneAppender 的實現細節雖然本文不會詳細討論,但是要重點說下分析器的問題。
4.5.1.需求
日志記錄的場景整體上還是比較明確的,有哪些信息會被記錄到日志中相對比較容易被預見到,根據前述的配置方案, Lucene 中具體的索引項,是通過 IndexField 標簽進行配置的,這些項目整體上可分為兩類,一類是有具體含義的字段,比如時間,一類是內容不確定的字段,比如日志的內容,對于有具體含義的字段,應該不分詞,查詢時精確匹配,而對于像內容這樣的內容不明確字段,也應該是不分詞,但是查詢時采用模糊匹配,這樣的設計針對日志查詢這個場景來說,還是比較合理的。
4.5.2.常見分析器對比
Lucene 內置了很多的分析器,常見的比如: WhitespaceAnalyzer 、 SimpleAnalyzer 、 StopAnalyzer 、 StandardAnalyzer 、 CJKAnalyzer 、 KeywordAnalyzer 等,它們各自特點如下:
分析器
空格拆分
符號拆分
數字拆分
無用詞拆分
文字轉小寫
標記文本類型
中日韓文字處理
WhitespaceAnalyzer
是
否
否
否
否
否
無
SimpleAnalyzer
是
是
是
否
是
否
無
StopAnalyzer
是
是
是
是
是
否
無
StandardAnalyzer
是
部分
否
否
是
是
逐個拆分
CJKAnalyzer
是
部分
否
是
是
是
雙字拆分
KeywordAnalyzer
否
否
否
否
否
否
無
4.5.3.結論
根據上述的需求分析,以及對現有的常見分析器對比, KeywordAnalyzer 分詞器是比較合適的,但是,它有兩個約束,一個是區分大小寫,如果希望不區分大小寫,則需要進行相應的擴展開發,一個是關鍵字不能多于256個字符,這個約束應該問題不大。
5.優缺點
5.1.優點
- 資源占用少 :日志的記錄過程,和常規日志的記錄沒多大區別,沒有額外的CPU和內存占用,唯一有較大消耗的,是 Lucene 的索引文件需要占用磁盤空間,如果對占用磁盤空間敏感,或者對日志的長期保存沒有嚴格要求,本方案中可以設定索引過期時間,超期的索引會被刪除。整個方案是可以不需要額外的服務器軟硬件資源進行部署的;
- 部署簡單 :本方案只需要開發一個單獨的代理模塊和應用部署在一起即可,另外就是查詢界面,可以根據需要集成在相應的系統中即可,比如監控系統等;
- 靈活性強 :可以根據需要,在日志配置文件中進行靈活的配置,可以配置記錄哪些項目,什么格式等,查詢界面也可以靈活地定制,根據需要以適應具體的業務場景;
- 開發學習簡單 :本方案中需要學習的技術不多,只需要熟悉 Ignite 和 Lucene 的開發即可,查詢界面可以根據需要而定,沒有嚴格要求。
5.2.缺點
- 需要一定的開發量 :這不是一套開箱即用的解決方案,包括查詢界面等,是需要開發的,但是工作量不大,如果還有其他的需求,比如日志分析等,只能自行開發;
- 依賴Ignite的集群部署 :這套方案查詢的范圍是Ignite構建的集群或者在這個集群范圍內定義的集群組。因此一方面應用要通過Ignite構建集群,另外一方面如果集群內應用較多,為了避免相互干擾,對集群分組也是必要的,這方面可以算做一個強約束。
6.其他的相關方案
僅就開源社區而言,還存在 Elastic Stack 、 Flume 等日志處理技術,功能各有側重,但如果僅僅想做分布式日志的查詢的話,這些方案略重,如果這些方案不滿足需求需要開發,則工作量略大,以 Elastic Stack 為例,整個技術棧使用的技術較多,對開發者要求較高,整體定制成本較高。另外這些方案都需要額外,甚至較多的服務器軟硬件資源,部署成本較高。
7.適用領域
這套方案整體上來說適用于,或者說面向的是以集群方式部署的企業級交付型軟件,廠商可以不受約束的掌控整個方案的方方面面,對客戶來說,部署成本也較低。這套方案對于軟件的規模沒什么限制,整個集群有很多個應用也可以。這類應用對日志處理的需求,功能邊界定義可以做到比較清晰,需求不會擴的很大,這樣的話,定制ELK解決方案的代價就顯得太大了。
而對于互聯網公司來說,內部有好多錯綜復雜的系統,數量可能幾十上百,更多的都有,這時在ELK整個技術棧的基礎上進行定制,可能更劃算,引入這套方案甚至可能都不可行。
8.總結
本方案另辟蹊徑,通過新的技術棧,較少的代碼解決了長期困擾分布式日志查詢這個行業痛點,另外,本方案也開闊了思路,即 Ignite 這種嵌入式的分布式計算技術, MapReduce 計算方式,有非常多的使用場景,不僅僅可以用于常規的計算,還可以用于各種集群環境的數據收集等場景,想象空間很大。
本方案中 Lucene 作為全文檢索領域的行業標準,得到了廣泛的應用,大量的解決方案都基于 Lucene 進行定制開發, Elastic Stack 底層也構建于 Lucene 之上。
如果本方案能稱為最佳實踐,那么 Ignite 功不可沒。這一類的技術還有其他技術可選,比如 Infinispan 等,只是這一類的開源嵌入式內存并行計算技術,還沒有得到業界的關注而已。
來自:http://www.infoq.com/cn/articles/ignite-lucene-log4j2-log-query