【編者按】本文做者是 Archanaa Panda ,從 2000 以來一直在軟件開發(構架、設計和編程)團隊擔任 Java / JavaEE 構架師,目前立志於作一個與時俱進的獨立的顧問架構師。在本篇文章中,做者經過多個方面爲生產環節的日誌提供建議和指導,最後還介紹了一個高性能的智能日誌技術,幫助你們構建高性能的智能日誌框架。
html
當應用在生產過程當中,日誌一般處於開發週期的次要位置,但實際上高性能的日誌可能成爲開發團隊的重要生命線。在此咱們假設讀者已熟悉了各類日誌框架,如 Log4j 、 SLF4J 等,因此再也不詳細介紹,本文旨在爲「真實」的生產日誌提供指南,檢測其對應用質量的影響,同時還爲你們介紹了一個被遺忘已久的高性能的智能日誌技術。java
在爲應用搭建架構、設計、開發甚至是提高性能的整個環節中,你們都經常忽略日誌的重要性。最後當應用程序一切準備就緒打算部署的時候,會發生什麼呢?數據庫
糟糕!應用程序已經脫離你的開發環境,它沒法運行出你想要的 IDE 平臺,並且沒有對應的調試器可用,此時你纔想起來日誌的重要性彷佛爲時已晚。而後當你費勁地想從一大堆日誌中嘗試調試出應用哪裏出了問題,才發現這個任務不只僅是艱鉅,並且極其無聊、繁瑣,還會浪費大量的時間。根據你多年的經驗和專業知識來看,這樣的作法毫無疑問是大海撈針!再或者你把這個任務分配給下屬或運維團隊,而這樣一份繁瑣無比費力不討好的工做,擱誰身上都會招致抱怨!甚至他們也無功而返,集體抱怨你的失策,你只有讓專業架構師和設計師參與進來。apache
因此,日誌應該是生產應用的重要生命線,誰都不該該掉以輕心。固然,衆所周知,能夠根據需求開啓或關閉各類日誌的記錄級別,市面上知道有多種日誌類別和日誌框架,如 Log4j 、 Commons Logging 或是 SLF4J ,咱們能夠直接將日誌發到不一樣目的地,如文件、數據庫、 JMS 隊列等。編程
可是咱們中有多少人會真正地計劃日誌呢?又有多少人理解日誌是如何影響系統質量的呢?誰又會不斷地去優化日誌,時刻記得一旦應用上線,日誌會對系統和工做生活產生哪些影響?還有多少人已經嘗試過利用日誌框架的先進功能來提高日誌性能呢?後端
本文主要想喚醒你們對應用日誌的重視,同時爲部署應用日誌提供基本指導。最後,本文會介紹一個被遺忘已久的日誌技術,在不影響應用性能和質量的前提下,幫助你們實現高質量的日誌記錄。數組
日誌記錄是最多見的質量監控方式,它能夠幫助應用開發者探究到問題的根源。但這些每每只經過監控來實現,對於開發者來講,在代碼的各個地方編寫一個 System.out.println() 或 logger.log() 語句再簡單不過。然而,問題在於大量的日誌可能會致使有用的事件日誌被忽略掉,這樣徹底達不到日誌記錄的目的。因此,開發者可能會尋找其餘系統監控技術,如使用或開發 JMX 控制檯。這一點會在後面的 「爲待生產的應用進行日誌記錄」章節中詳細討論。安全
無論諮詢哪位性能專家或架構師,對於 90% 的應用來講,過多的日誌記錄都會對性能產生很是不利的影響。日誌是一種 I/O 密集型活動,的確會對應用性能產生不良影響,特別是傳統的日誌方式還會寫入應用線程環境的 FileAppender 中。不只如此,日誌代碼還會形成大量的堆消耗和垃圾收集,好比:服務器
if (logger.isDebugEnabled()) logger.debug("name "+person.name+" age "+person.age+" address "+person.address);
除此以外,內部調用日誌到 Log4J Appender 的 doappend() 方法,會與線程安全同步。這也意味着,應用線程不只在同步地進行大量的磁盤 I/O 操做,還會在寫入日誌時互相阻塞!在某電子政務門戶網站上最嚴峻的性能情形之一就是,線程轉儲顯示在日誌記錄寫入到單個文件 appender 時,應用日誌也被寫入到這個的 appender 文件集中!網絡
事實上,性能專家首先會肯定應用的當前日誌級別,而後一般只是把日誌級別從 DEBUG 切換 INFO 或 WARNING 模式,來達到提高應用性能的目的。可是,在完成性能基準測試工做或即時可伸縮問題以後,應用開發者在尋找應用功能性 bug 的根本緣由時,又會將日誌級別改回到 DEBUG 模式。事實上,這並非一個科學的日誌操做。在「爲待生產的應用進行日誌記錄」中,咱們還會進一步討論日誌規範和衛生維護。「高性能質量的日誌記錄」中也會詳細闡述在不影響應用性能的前提下可以實現質量記錄的相關技術。
審覈日誌是一類特殊的日誌,主要用於應用的安全審覈和跟蹤用戶操做。如下示例主要介紹了日誌在安全性方面的幫助。
可是,若是走向另外一個極端,日誌中攜帶的敏感信息,如用戶的賬戶密碼,可能會暴露系統漏洞。
第三,記錄應用程序的事件和流程可能有助於開發者監控和調試,但同時可能會無心地暴露應用的內部架構。
在當前的雲應用環境中,應用能夠在公共雲上託管,這樣的漏洞會對應用全部者的知識產權構成威脅。
日誌記錄會和擴展性、高可用性之間互相影響。利用「高性能質量的日誌」技術來提升應用性能,用更少的硬件提升擴展性和可用性,在現有的資源和條件下,你的應用徹底能夠「重拳出擊」。
當應用被擴展成可以對可用性進行主動或被動配置時,就會有多個應用實例,而日誌策略就顯得很是重要。應用是否能支持,或者開發團隊是願意收集來自10個不一樣機器或目錄的日誌,仍是找一個位置能集中收集日誌呢?此時,集中的分佈式日誌變得相當重要。
像如 Oracle 這樣的重要數據庫,已經使用 Redo 日誌來確保事務恢復。應用也能夠參考這種作法,使用一類特殊的日誌幫助恢復,以防萬一。
在大多數應用中,日誌只是其中一種錯誤處理方式,有時只用來評估錯誤。在複發性錯誤,如短信/郵件服務器或數據庫長期不可用的狀況下,重複地、頻繁地記錄錯誤是百害而無一利,特別在大量的異常堆棧跟蹤下,只會大大地增長 I/O 活動。在這個過程當中,當你要分析一個星期前被忽略的老問題時,這時候關於這個問題的日誌早已滾動更新,這種方法只會「火上澆油」。
在考慮應用的容量問題時,架構師會參考生產環節的日誌大小,再估計所需的磁盤空間、集中文件系統的配置等。
對於分佈式環境中的集中式日誌,也須要估計分佈在網絡到遠程機器的日誌對象的大小。
本節主要介紹做者對嵌入式應用案例的研究經驗,若是在架構、設計和開發階段忽略了日誌記錄的重要性,問題一旦發生,以前所作的一切可能功虧一簣,只得在慘痛的教訓面前學着「吃一塹,長一智」。
舉一個你們都熟悉的場景, GPS 設備就是一個嵌入式應用,裝載在車中能夠進行位置跟蹤。該設備不提供任何用戶界面,除了 LEDs 和幾個按鈕,因此幾乎沒有人來管理車輛內部的應用程序,不像豪華車型那樣會和服務器端應用進行交互。所以,若是設備應用出現了問題,應用開發者應該如何診斷問題根源呢?隨着各類卡車被運往全國各地,日誌又是什麼時候寫入車內設備的呢?
應用開發者可能會異想天開,全國各地都配備了服務工程師,可以取下設備帶回去進一步分析。以上設想純屬虛構,實際上,開發者只會和服務工程師登入設備的操做系統去複製日誌。但面對繁雜的日誌,連續的加班做戰只會讓人身心疲憊!
爲了更方便地完成這項工做,應用開發者在設備的桌面服務應用中加上「日誌下載」按鈕。服務工程師就能夠直接利用筆記本里的服務應用下載相關的設備日誌。這至少是一個進步,至少沒必要再讓車停下來再取走設備了,也給了本該陪同服務工程師夜以繼日下載日誌的可憐開發者們一絲喘息的空間。顯然,服務工程師也不至於全世界亂跑了,他們只要盯着應用開發團隊注意下載日誌便可。
最後,開發團隊不得不提升設備應用的性能,讓它像發送其餘跟蹤數據同樣,直接經過無線 GPRS 就能夠發送日誌到後端服務器。
須要注意是,在初始的預估和開發過程當中,全部這些額外工做都沒有被計入需求池或預算中。開發團隊已經有一個典型的由客戶功能需求驅動的思惟定勢。應用日誌既不是一個客戶驅動的需求,也並不是是突出的非功能屬性。因此新手開發者一般缺少遠見,也無力說服高層或管理者給他們足夠的時間預算來創建這樣的設施。當這些設備準備上市時,他們遇到了這樣的問題,經理和客戶氣得臉紅脖子粗,而他們不得不挑燈夜戰。真是一個費力不討好的活兒!
and don'ts in this section, which would make an application production ready.
在上一節「記錄其餘系統質量屬性」中,咱們已經遇到了一些在生產環境中不得不面臨的問題。接下來在本節中,咱們再羅列出哪些該作和哪些不應作,爲應用的待生產狀態作準備。
- 日誌規範和代碼審查
日誌規範極其重要,由於這一步將爲本文討論的其餘最佳實踐鋪平道路。事實上,許多生產系統還會有那些無聊的日誌,如 「 Hi 」 、 「 Came over here 」、「 Done 」、「 xxxyyyyzzzz 」。這些日誌一般會在應用調試階段或開發階段的單元測試中產生。
但生產階段還有人仍然採用這樣的無聊日誌,其給出的理由是它們只會在調試時產生,並且便於關閉。可是,在實踐中開發者不多這樣作,關閉的同時也關掉了一些有價值的日誌。爲了更好地控制日誌,須要開發者很是精細地配置日誌框架,但在生產中卻經常忽略這一點
同時,代碼審查必須提升效率。當高級開發者或團隊領導審查日誌時,必須確保刪除掉無用的日誌,即便要面對一些挑釁的言論,好比有人說「我已經在測試時刪過日誌了」,「不就是個日誌麼!它又不是引起問題的緣由」。但這是一個很好的規則,日誌並非開發過程當中用來調試問題的方式,負責調試的是咱們 IDE 的調試器!
- 當你對應用進行模塊化時,也須要關注集中式日誌
應用日誌寫入應用服務器的 SystemOut 或 SystemError 文件顯示不是最高效的辦法,但在生產環境中仍然常見,或如前所述,電子政務門戶的線程互相阻塞,共同爭奪一個 FileAppender 或者一個文件 I/O 。
最起碼,開發者應該將有基於軟件包或徹底限定類爲已有的記錄器命名約定,並可能將不一樣的日誌分類記錄到不一樣的 Appender 位置。
在生產過程當中,應該牢記日誌級別應該是 WARNING 或根據日誌信息設爲 INFO 級別。
一個有效的方法是在中央配置或常量類中,列舉出全部可能的日誌,並只容許開發者使用這些日誌。咱們將在「設計高性能的智能日誌」章節中進行討論。
- 在集羣環境和分佈式環境中記錄日誌
幾乎全部的服務器端應用都必須採用集羣和分佈式環境,由於這兩種技術能夠提供可擴展性和可用性。在集羣環境中,日誌應該反映出組件、模塊、子系統以及其過程實例。
在分佈式和集羣的環境採用集中的日誌服務器,能夠避免從多個目錄和機器上收集日誌的繁瑣。同時,對於移動 I/O 到一個單獨的機器上也更加方便,而應用性能能夠再也不受日誌 I/O 的影響。
- 在厚分佈式客戶端或嵌入式應用中進行日誌記錄
在「缺乏重要日誌的案例分析」章節中的趣聞中也提道,在厚客戶端或嵌入式應用的日誌幾乎不會發送給到開發團隊,也無從幫助他們進行問題分析。遠程傳輸日誌的機制須要再深思熟慮,再適當地列入項目計劃進行開發。
- 使用映射診斷環境和嵌套診斷環境
Log4j 的映射診斷環境( MDC )和嵌套診斷環境( NDC )使用 ThreadLocal 儲存環境特定信息。它們能夠存儲如用戶名、事務 ID 這樣的信息,來識別特定用戶或事務所作的所有操做。這就不須要爲了日誌記錄,在類和方法中傳遞特定環境信息。利用 PatternLayout 的 %X 或 X { key } ,存儲的值將在日誌中呈現。
- 規劃日誌的生命週期和維護
這包括規劃日誌滾動更新以前的日誌文件大小和最大數量。爲何須要規劃呢?由於日誌文件經常大到用文本編輯器都打不開!正如腳本會按期對數據庫進行備份同樣,也應該有腳原本備份和歸檔日誌。當超出磁盤空間限制時,壓縮日誌文件也是不錯的想法,這樣遠程傳輸起來會更加容易。
- 抵制實時記錄源位置信息的誘惑
獲取位置信息經常以昂貴的性能損失爲代價,由於日誌框架試圖肯定當前的線程堆棧,從而得到該方法、文件名和行數。確切地說,日誌信息自己就能夠經過提供服務器、子系統、模塊、組件、線程等信息找出日誌的來源。
- 避免重複使用長堆棧跟蹤來記錄錯誤
若是可能的話,日誌中應該有足夠的信息顯示錯誤發生的位置,並儘量避免巨大的堆棧跟蹤。固然,這不是一個像 NullPointerException 那樣的特例。但它能夠爲一些容易識別的特定應用錯誤進行記錄。此外,當常常性問題長期發生時,如與 Email 、短信或數據庫服務器的鏈接問題,日誌記錄也會每隔5分鐘地記錄該問題,而不是每隔幾秒就用巨大的堆棧跟蹤填充日誌。
- 不要盲目使用 AOP 注入記錄,尤爲在生產過程當中
對於新手來講,最基本的 AOP 教材案例就是日誌。由於日誌自己就是一個橫切關注點,新手能夠在進入方法以前或退出方法以後注入日誌。在應用於生產有價值的應用以前,應該嚴肅地考慮這個示例或觀點。對於以上已經創建的示例,日誌記錄可不是一件小事,它值得像大多數其餘非功能性需求( NFRs )同樣進行詳細布局規劃。
- 別把日誌看成其餘監控手段的替代品
最滑稽地使用日誌記錄的典型案例之一就是「性能日誌」,以下所示:
19 Sept 2010 10:20:30 PERF INFO Thread-25 OrderInsertAction.java Time taken in processing OrderInsertAction: 50ms 19 Sept 2010 10:20:33 PERF INFO Thread-8 OrderInsertDao.java Time taken in insert 30ms
筆者就曾犯過這樣的問題,卻沒意識到它增長 I/O 對性能產生的嚴重危害。
更明智的作法是捕獲 「 TimeStatistic 」 的總時間,並用計數器算出用 GUI 屏幕顯示一樣內容的平均時間、最長時間、最短期。
在這一節中主要討論的策略是將集中式日誌包裝成記錄器,日誌採用整數編碼而不是字符串。這項技術已經由做者在導師的建議下成功地實現。
目前在網上有許多文章都介紹如何用 JMS 隊列和主題或 sockets 來構建集中式日誌記錄。集中式日誌記錄可以經過移動 I/O 活動到不一樣的機器上進一步提升性能,雖然會對程序節點有輕微的開銷。
可是,這裏的關鍵是結合代碼來記錄集中式日誌,而非冗長的字符串。現有框架 Log4J 或者 Commons Logging 鼓勵使用字符串來記錄信息,這樣的做法會對內存、磁盤和網絡資源形成必定影響,而這些工做徹底能夠經過一段簡單的代碼搞定。
一個單獨的文件能夠列出錯誤代碼和可識別字符串之間的映射。
如如下日誌記錄:
[090822 16:02:48] TX WARNING (Tx-2-thread-1: 1163 transmitData): Server has not responded with an ACK, so trying again.
與下面的日誌進行對比
1300604499194,4,192168001002,20600,1001,2,500000
以上日誌顯示了長時間戳、日誌級別、生成日誌的機器IP地址、處理的整數值、處理模塊、應用的實例ID和一個完整的錯誤代碼,代碼翻譯過來也會傳達相同的含義。這種對象很是便於在網絡中以二進制格式進行傳輸。若是有上下文信息能進一步限定日誌中的信息,一個 Object[] 數組也能夠被傳遞,而主錯誤代碼將轉化成爲帶有 printf() 格式佔位符的字符串。
使用短碼錶示錯誤的作法,幾乎在全部的主流產品中都很常見,如 Oracle 、 WebSphere 、 Microsoft 。例如,在微軟的 Office 應用出現錯誤時,所反饋的錯誤對話框就是一個難以讀懂的整數代碼,而後會發送給微軟用於診斷。
在查看錯誤時,能夠將各類錯誤代碼翻譯成完整的字符串進一步解讀。
這樣作的好處有以下幾點:
能節省磁盤空間,從而延長日誌的保質期和縮減文件大小
應用內部設計和執行的一些安全措施不會被日誌暴露。但在脫機查看時,日誌能夠經過使用翻譯機來翻譯全文。
避免構造或傳輸長字符串,進一步減小內存使用。
網絡傳輸中日誌是很是輕量的,因此在調試日誌時也儘量保持最小開銷。
防止日誌隨機構造
經過自定製工具更高效地搜索特定錯誤
此外,經過防止直接使用 Log4J 或其餘相似的框架能夠執行日誌記錄,或者在你最喜歡的日誌框架上或自定製日誌上編寫一個定製格式,好比:
public class LogClientFacade { public void log(int logLevel, int instanceId, int subsystemId, int componentId, int errorCode); public void logWithContext(int logLevel, int instanceId, int subsystemId, int componentId, int errorCode, Object[] contextInfo); public void logWithEx(int logLevel, int instanceId, int subsystemId, int componentId, int errorCode, Throwable ex); ... }
這樣的日誌接口能確保開發者注意到在分佈式環境下診斷日誌的基本信息,好比子系統、部件或其餘,沒必要在在實時操做中記錄源代碼的行號和文件名。
上圖顯示了一種解決方案的建議設計。其目的是經過在隊列和異步處理中收集信息,或在接收器線程中進行轉移,儘可能確保日誌的處理過程不存在阻塞。這種方式在網絡傳輸過程當中,以二進制序列化格式進行信息傳輸具備諸多優點,特別是完整的解決方案是同步地使用同一語言時。
當他們倒進服務器查看離線日誌時,應該有一個簡單的圖形用戶界面來觀看日誌。而後用一個翻譯機將日誌轉換爲文本格式,而日誌自己也會以二進制格式寫入磁盤。須要注意的是,這種方法能和雲環境很好地關聯,從而確保部署的知識產權的保密性。
擴展示有框架也是一種好方法,如 Log4J 、 Commons Logging 、 SLF4J 。可是,這樣作的話,爲了遵循框架內部API的通用性,可能會犧牲一部分效率。例如, Log4J 會序列化日誌消息,而堆棧跟蹤會做爲字符串在 SocketAppender 和 JMSAppender 中進行網絡傳遞。該框架至關易於擴展,並且能覆蓋所選擇框架的特定部分,如經過添加或擴展新的 Appenders ,擴展內部的數據傳輸對象,如 LoggingEvent ,並進行自定義序列化。再者,若是須要最大的靈活性,你能夠僅用較短的時間來建立一個簡單的自定義日誌框架。
另外一個有趣的決定是在應用運行在服務器時是否使用 JMS ,或者經過使用一個獨立隊列,如 WebSphereMQ 、HornetQ 或 ActiveMQ 。若是選擇 JMS ,如下是做者的幾點建議:
使用寬鬆質量屬性來避免增長事務、持續性並容許隊列重複。記住,嚴格可靠性會下降性能,對日誌而言這是沒必要要的。
在一個 JVM 中,要麼是在日誌服務器或在客戶子系統,最好是使用輕量的 java.util.concurrent 隊列或 in-VM 隊列實現,從而避免系列化開銷。
建議使用消息代理或運輸橋樑,而不是用一個集中式隊列,並作相同的遠程調用。
本人的我的偏好是使用簡單的 socket。
在這篇文章中,咱們已經討論了最佳實踐和日誌記錄中所發現的弊端。咱們還提出了一種結合集中式日誌和代碼的技術,從而取代字符串實現高性能的智能日誌實踐。
做者將構思高性能智能日誌的設計歸功於她的導師 Akash Gupt 先生,是 InterGlobe 科技公司的解決方案架構師( http://in.linkedin.com/pub/akash-gupta/3/79/2b3 ),在他的指導下,做者成功地實施並觀察到這種技術的巨大性能優點。
8References
Pro Apache Log4J by Samudra Gupta
(編譯自:https://dzone.com/articles/high-performance-and-smarter)
OneAPM 爲您提供端到端的 Java 應用性能解決方案,咱們支持全部常見的 Java 框架及應用服務器,助您快速發現系統瓶頸,定位異常根本緣由。分鐘級部署,即刻體驗,Java 監控歷來沒有如此簡單。想閱讀更多技術文章,請訪問 OneAPM 官方技術博客。
本文轉自 OneAPM 官方博客