java並發系列 – 第28天:實戰篇,微服務日誌的傷痛,一併幫你解決掉
- 2019 年 10 月 3 日
- 筆記
這是java高並發系列第28篇文章。
環境:jdk1.8。
本文內容
- 日誌有什麼用?
- 日誌存在的痛點?
- 構建日誌系統
日誌有什麼用?
- 系統出現故障的時候,可以通過日誌資訊快速定位問題,修復bug,恢復業務
- 提取有用數據,做數據分析使用
本文主要討論通過日誌來快速定位並解決問題。
日誌存在的痛點
先介紹一下多數公司採用的方式:目前比較流行的是採用springcloud(或者dubbo)做微服務,按照業拆分為多個獨立的服務,服務採用集群的方式部署在不同的機器上,當一個請求過來的時候,可能會調用到很多服務進行處理,springcloud一般採用logback(或者log4j)輸出日誌到文件中。當系統出問題的時候,按照系統故障的嚴重程度,嚴重的會回退版本,然後排查bug,輕的,找運維去線上拉日誌,然後排查問題。
這個過程中存在一些問題:
- 日誌文件太大太多,不方便查找
- 日誌分散在不同的機器上,也不方便查找
- 一個請求可能會調用多個服務,完整的日誌難以追蹤
- 系統出現了問題,只能等到用戶發現了,自己才知道
本文要解決上面的幾個痛點,構建我們的日誌系統,達到以下要求:
- 方便追蹤一個請求完整的日誌
- 方便快速檢索日誌
- 系統出現問題自動報警,通知相關人員
構建日誌系統
按照上面我們定的要求,一個個解決。
方便追蹤一個請求完整的日誌
當一個請求過來的時候,可能會調用多個服務,多個服務內部可能又會產生子執行緒處理業務,所以這裡面有兩個問題需要解決:
- 多個服務之間日誌的追蹤
- 服務內部子執行緒和主執行緒日誌的追蹤,這個地方舉個例子,比如一個請求內部需要給10000人發送推送,內部開啟10個執行緒並行處理,處理完畢之後響應操作者,這裡面有父子執行緒,我們要能夠找到這個裡面所有的日誌
需要追蹤一個請求完整日誌,我們需要給每個請求設置一個全局唯一編號,可以使用UUID或者其他方式也行。
多個服務之間日誌追蹤的問題:當一個請求過來的時候,在入口處生成一個trace_id,然後放在ThreadLocal中,如果內部設計到多個服務之間相互調用,調用其他服務的時,將trace_id順隨身帶過去。
父子執行緒日誌追蹤的問題:可以採用InheritableThreadLocal來存放trace_id,這樣可以在執行緒中獲取到父執行緒中的trace_id。
所以此處我們需要使用InheritableThreadLocal
來存儲trace_id。
關於ThreadLocal和InheritableThreadLocal可以參考:ThreadLocal、InheritableThreadLocal(通俗易懂)
如果自己使用了執行緒池處理請求的,由於執行緒池中的執行緒採用的是復用的方式,所以需要對執行的任務Runable做一些改造,如程式碼:
public class TraceRunnable implements Runnable { private String tranceId; private Runnable target; public TraceRunnable(Runnable target) { this.tranceId = TraceUtil.get(); this.target = target; } @Override public void run() { try { TraceUtil.set(this.tranceId); MDC.put(TraceUtil.MDC_TRACE_ID, TraceUtil.get()); this.target.run(); } finally { MDC.remove(TraceUtil.MDC_TRACE_ID); TraceUtil.remove(); } } public static Runnable trace(Runnable target) { return new TraceRunnable(target); } }
需要用執行緒池執行的任務使用TraceRunnable
封裝一下就可以了。
TraceUtil程式碼:
public class TraceUtil { public static final String REQUEST_HEADER_TRACE_ID = "com.ms.header.trace.id"; public static final String MDC_TRACE_ID = "trace_id"; private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>(); /** * 獲取traceid * * @return */ public static String get() { String traceId = inheritableThreadLocal.get(); if (traceId == null) { traceId = IDUtil.getId(); inheritableThreadLocal.set(traceId); } return traceId; } public static void set(String trace_id) { inheritableThreadLocal.set(trace_id); } public static void remove() { inheritableThreadLocal.remove(); } }
日誌輸出中攜帶上trace_id,這樣最終我們就可以通過trace_id找到一個請求的完整日誌了。
方便快速檢索日誌
日誌分散在不同的機器上,如果要快速檢索,需要將所有服務產生的日誌彙集到一個地方。
關於檢索日誌的,列一下需求:
- 我們將收集日誌發送到消息中間件中(可以是kafka、rocketmq),消息中間件這塊不介紹,選擇玩的比較溜的就可以了
- 系統產生日誌盡量不要影響介面的效率
- 頻寬有限的情況下,發送日誌也盡量不要去影響業務
- 日誌盡量低延次,產生的日誌,盡量在生成之後1分鐘後可以檢索到
- 檢索日誌功能要能夠快速響應
關於上面幾點,我們需要做的:日誌發送的地方進行改造,引入消息中間件,將日誌非同步發送到消息中間件中,查詢的地方採用elasticsearch,日誌系統需要訂閱消息中間件中的日誌,然後丟給elasticsearch建索引,方便快速檢索,咱們來一點點的介紹。
日誌發送端的改造
日誌是有業務系統產生的,一個請求過來的時候會產生很多日誌,日誌產生時,我們盡量減少日誌輸出對業務耗時的影響,我們的過程如下:
- 業務系統內部引用一個執行緒池來非同步處理日誌,執行緒池內部可以使用一個容量稍微大一點的阻塞隊列
- 業務系統將日誌丟給執行緒池進行處理
- 執行緒池中將需要處理的日誌先壓縮一下,然後發送至mq
執行緒池的使用可以參考:JAVA執行緒池,這一篇就夠了
引入mq存儲日誌
業務系統將日誌先發送到mq中,後面由其他消費者訂閱進行消費。日誌量比較大的,對mq的要求也比較高,可以選擇kafka,業務量小的,也可以選取activemq。
使用elasticsearch來檢索日誌
elasticsearch(以下簡稱es)是一個全文檢索工具,具體詳情可以參考其官網相關文檔。使用它來檢索數據效率非常高。日誌系統中需要我們開發一個消費端來拉取mq中的消息,將其存儲到es中方便快速檢索,關於這塊有幾點說一下:
- 建議按天在es中建立資料庫,日品質非常大的,也可以按小時建立資料庫。查詢的時候,時間就是必選條件了,這樣可以快速讓es定位到日誌庫進行檢索,提升檢索效率
- 日誌常見的需要收集的資訊:trace_id、時間、日誌級別、類、方法、url、調用的介面開始時間、調用介面的結束時間、介面耗時、介面狀態碼、異常資訊、日誌資訊等等,可以按照這些在es中建立索引,方便檢索。
日誌監控報警
日誌監控報警是非常重要的,這個必須要有,日誌系統中需要開發監控報警功能,這塊我們可以做成通過頁面配置的方式,支援報警規則的配置,如日誌中產生了某些異常、介面響應時間大於多少、介面返回狀態碼404等異常資訊的時候能夠報警,具體的報警可以是語音電話、簡訊通知、釘釘機器人報警等等,這些也做成可以配置的。
日誌監控模組從mq中拉取日誌,然後去匹配我們啟用的一些規則進行報警。
結構圖如下
關於搭建日誌中遇到的一些痛點,可以加我微信itsoku交流。
構建日誌系統需要用到的知識點
- java中執行緒池的使用
- ThreadLocal、InheritableThreadLocal(通俗易懂)
- elasticsearch,可以參考其官方文檔
- mq
java高並發系列目錄
- 第1天:必須知道的幾個概念
- 第2天:並發級別
- 第3天:有關並行的兩個重要定律
- 第4天:JMM相關的一些概念
- 第5天:深入理解進程和執行緒
- 第6天:執行緒的基本操作
- 第7天:volatile與Java記憶體模型
- 第8天:執行緒組
- 第9天:用戶執行緒和守護執行緒
- 第10天:執行緒安全和synchronized關鍵字
- 第11天:執行緒中斷的幾種方式
- 第12天JUC:ReentrantLock重入鎖
- 第13天:JUC中的Condition對象
- 第14天:JUC中的LockSupport工具類,必備技能
- 第15天:JUC中的Semaphore(訊號量)
- 第16天:JUC中等待多執行緒完成的工具類CountDownLatch,必備技能
- 第17天:JUC中的循環柵欄CyclicBarrier的6種使用場景
- 第18天:JAVA執行緒池,這一篇就夠了
- 第19天:JUC中的Executor框架詳解1
- 第20天:JUC中的Executor框架詳解2
- 第21天:java中的CAS,你需要知道的東西
- 第22天:JUC底層工具類Unsafe,高手必須要了解
- 第23天:JUC中原子類,一篇就夠了
- 第24天:ThreadLocal、InheritableThreadLocal(通俗易懂)
- 第25天:掌握JUC中的阻塞隊列
- 第26篇:學會使用JUC中常見的集合,常看看!
- 第27天:實戰篇,介面性能提升幾倍原來這麼簡單
java高並發系列連載中,總計估計會有四五十篇文章。
阿里p7一起學並發,公眾號:路人甲java,每天獲取最新文章!