一、背景
系統日誌可用於追蹤使用者操作軌跡,異常情況下,合理的日誌有助於快速排查、定位問題,毫無疑問,打印日誌對於系統是很重要的。
當業務規模較小時,大家都傾向於享受日誌帶來的便利,從而忽略日誌帶來的潛在的負面影響,缺乏對日誌的管控。在JD當前使用者量、業務規模下,絕大多數C端系統、甚至B端系統都是高吞吐的,毫無疑問,過大的日誌量對系統的效能、磁盤IO有著顯著負面影響,趕上大促時,問題尤為突出。日誌在為我們提供便利的同時,也無時無刻成為一根刺,時不時刺我們一下。
作為一個共性問題,由於集團暫沒推出統一的日誌框架,不少團隊都會嘗試基於log4j、logback 進行輕度的封裝,透過跟配置中心聯動,增加一些諸如 '動態降級' 的功能、來緩解日誌帶來的負面影響。降級帶來的效果是顯著的,但同時也讓系統喪失了記錄 '操作軌跡' 的能力,從而又帶來了新的問題。
此時,很容易想到,可以透過對 '請求' 采樣,實作請求日誌的采樣輸出,並透過控制采樣比例平衡不同場景下日誌對效能的影響,系統吞吐量較大時,降級采樣比例,系統吞吐量較低時,提高采樣比例。
二、正文
Ⅰ
在請求入口處,透過一定的采樣演算法,計算當前請求是否采樣,並借助 Java 的 ThreadLocal 機制,可以實作當前請求執行緒的日誌采樣。但是,大部份關註日誌效能的系統,往往處理邏輯也是復雜的,會有各樣的異步運算邏輯,因此,大量的日誌不一定來自請求執行緒,更多的可能來自子執行緒、執行緒池執行緒,甚至巢狀線上程池執行緒下的執行緒。
如何將請求執行緒、子執行緒、執行緒池執行緒統一協調起來,實作 '采樣標識' 的跨執行緒透傳,從而確保請求維度采樣的一致性 ,是本文關註的重點,希望可以給大家提供一些思路、並附上我們業務場景下的具體案例。
(1) 可以像 Transmittable Thread Local 一樣,透過對執行緒池類、或者任務類(Runnable、Callable)進行包裝,將透傳邏輯封裝起來,JD內部比較典型的用法有 pfinder、jade。
必須承認,使用這種方式實作是有一定優勢的,但是奈何現在各團隊都有自己獨特功能的執行緒池包裝類,並已經在業務系統中廣泛使用,如果此時再增加一個處理 '日誌采樣' 的包裝,會出現巢狀包裝的情況,這樣會讓程式變得混亂,增加理解成本,由於各包裝實作之間缺乏磨合,甚至面臨不可控的風險,這樣做甚至有 '添亂' 的嫌疑,我認為至少在當前的環境下是不可取的。
(2) 直接粗暴一些,在業務系統中,涉及子執行緒、執行緒池的地方,透過程式碼 '顯式的' 將 '采樣標識' 不斷透傳。
這樣做是能達到目的的,但是,會有大量的邏輯程式碼和業務耦合的情況(執行緒池越多,業務越復雜,耦合越嚴重),雖然單系統改造量不大(當然,這個也是因系統而異),但是放射線面太廣,需要各業務系統都要配合改造,同時有影響業務邏輯的風險,也不可取。
(3) 將請求執行緒的控制邏輯跟具體業務解耦開來,封裝為一個元件,並在元件中提供合適的 api,將元件復用於各業務系統,並根據業務系統具體情況,來決策是否使用 api 來處理異步執行緒(子執行緒、執行緒池執行緒的統一簡稱)的情況。
在沒有特別合適的、集團級統一api的情況下,這是一種較為務實的做法,透過接入抽象元件來控制請求執行緒的日誌采樣,並透過擴充套件 api 來協調請求執行緒和異步執行緒的采樣一致性,並根據業務系統的實際情況,來決策是否需要 '修改程式碼' 來協調異步執行緒。
如果業務系統中使用異步執行緒的處理邏輯較少,只接入元件,進行請求執行緒的日誌采樣即可;
如果業務系統中大量使用異步執行緒做邏輯,可接入元件再針對必要的地方透過擴充套件 api 來協調請求執行緒和異步執行緒的采樣一致性。
除上述外,應該還有其他的辦法,比如 AOP 機制,但是 AOP 只能到最低方法粒度,對於存量系統來說,改造量還是偏大,如果是新系統開發,可以考慮。
Ⅱ
最後,上面的思考都是偏向全域的,在具體實作時,需要考慮、斟酌的細節還有很多、下面拋磚引玉列出一些,希望對大家拓展思路有所幫助!
采樣的演算法,是 '隨機' 還是 '對 traceId 取模',然後透過配置中心控制取樣比例。 甚至跟入參關聯起來,對特定場景(對哪個介面、方法,按什麽規則進行入參篩選)進行采樣,即所謂 場景化采樣;
擴充套件 api 應該提供哪些功能,怎麽封裝能做到讓業務套用改動量最少?
怎麽保證全域(包括異步執行緒日誌)請求 traceId 的一致性,這方面其實 pfinder 也有方案可供參考;
采樣比例的最小粒度,是 百分之一、千分之一、還是萬分之一?
整系統使用一個采樣機率(即當前請求命中采樣時,各級別都打),還是各級別分別設定(分別設定時還要考慮聯動)
通常,我們傾向於,如果需要對當前請求進行采樣,可能 info、error 一塊打,也可能只打 error,但是不太可能只打 info 不打 error,所以需要有一個策略去控制;
當大量 error 產生時,怎麽收斂日誌?控制打印速率?甚至可以晉級一步,將磁盤IO 和打印速率聯動。
三、實踐
促銷交易這邊目前恰好在做這方面相關的技術改造,透過在目前既存日誌元件(透過 JD JSF 前置過濾器實作)基礎上,實作請求執行緒的日誌采樣,並提供擴充套件 api 給業務系統,實作整體上請求維度的日誌采樣,從而盡可能的減少業務系統的改造,以下流程圖描述了大致落地過程,供參考,有興趣的可以評論區留言進一步探討具體落地細節。
涉及異步執行緒時,使用擴充套件 API 改造也相對簡潔:
// 改造前,執行緒池任務執行邏輯
threadPoolExecutor.execute(() -> "your business logic");
// 使用擴充套件 api 改造後,對 '執行緒池執行邏輯' 進行包裝,實作 '采樣標識' 的跨執行緒透傳
threadPoolExecutor.execute(XxxUtils.wrap(() -> "your business logic"));
如喜歡本文,請點選右上角,把文章分享到朋友圈
如有想了解學習的技術點,請留言給若飛安排分享
因公眾號更改推播規則,請點「在看」並加「星標」 第一時間獲取精彩技術分享
·END·
相關閱讀:
作者:京東零售 盧吉欣/京東雲開發者,
來源:juejin.cn/post/7330504778562601012
版權申明:內容來源網路,僅供學習研究,版權歸原創者所有。如有侵權煩請告知,我們會立即刪除並表示歉意。謝謝!
架構師
我們都是架構師!
關註 架構師(JiaGouX),添加「星標」
獲取每天技術幹貨,一起成為牛逼架構師
技術群請 加若飛: 1321113940 進架構師群
投稿、合作、版權等信箱: [email protected]