log4j緩存與性能優化



    在軟件系統中,打日誌幾乎是每個系統都會使用的行爲。不管是用來記錄系統健康狀態,輔助問題定位,還是收集數據,以便後續數據分析等,日誌都起着舉足輕重的作用。但是IO的阻塞行爲和磁盤的讀寫速度低下意味着寫日誌並非是沒有代價的。

 

存在問題

          在很多系統中,日誌模塊用的都是log4j,打日誌用的都是同步方法,基本配置如下:

 

<appender name="appAppender" class="org.apache.log4j.DailyRollingFileAppender"> 
        <!-- <param name="Threshold" value="INFO" /> --> 
        <param name="File" value="/data/applogs/XXXXXXXX/logs/app.log"/> 
        <layout class="org.apache.log4j.PatternLayout"> 
            <param name="ConversionPattern" 
                   value="[hotel-order-service]%d [%t] %-5p [%X{HOTEL_LOG_TRACE_ID                          }] [%c %L] %m%n"/> 
        </layout> 
</appender>

 

  按照這種配置,日誌的輸出行爲是,在代碼層每次調用輸出日誌接口的時候,這條日誌就馬上寫入到磁盤中,java的io是阻塞的,而當前磁盤的讀寫性能低下是共知的。我隨機找了公司幾臺服務器用iostat命令查看了每次寫磁盤的時間,發現每次io最快也要1.5ms,慢的需要5.7ms!如果某個接口平均需要打5條日誌,那麼花在打日誌上的時間平均就需要10ms。爲了減少打日誌的時間,最主要的是減少往磁盤寫日誌的次數,但是如果不能從代碼層減少寫日誌的次數,那麼考慮從系統上減少往磁盤寫日誌的次數。

 

 

優化方法&測試 

     log4j的DailyRollingFileAppender的實現了緩存模式,即合併日誌輸出的模式,它先把所有的日誌都放到一個buffer數組裏,如果buffer滿了,就把buffer裏內容全部寫入磁盤,這樣可以大大減少調用磁盤的次數。buffer的大小默認是8K(不同的版本默認值可能不一樣)。舉個例子, 比如你每次要輸出1K的日誌,那麼你輸出8次,纔會調用1次磁盤io。爲了開啓合併日誌的方式,需要在配置把bufferedIO設置爲true,同時也可以根據系統實際情況設置bufferSize來改變buffer數組的大小。 

      爲了驗證緩存模式的性能,在某系統中,嘗試開啓了緩存模式,同時把buffersize設置爲50K。下面的配置是開啓log4j的緩存:

<appender name="appAppender" class="org.apache.log4j.DailyRollingFileAppender"> 
        <param name="bufferedIO" value="true" />    
         <!-- 50k爲一個寫單元 ,可以自己定義--> 
        <param name="bufferSize" value="51200" />
        <param name="File" value="/data/applogs/XXXXXXXX/logs/app.log"/> 
        <layout class="org.apache.log4j.PatternLayout"> 
            <param name="ConversionPattern" 
                   value="[hotel-order-service]%d [%t] %-5p [%X{HOTEL_LOG_TRACE_ID}] [%c %L] %m%n"/> 
        </layout> 
</appender>

 

 

          下面是開啓緩存模式前後的響應時間測試: 

       首先,需要保證服務器環境的一致性,因爲不同的服務以及服務器再不同的qps壓力下,響應時間也會不同。因此整個的測試數據收集時間再2個小時以內,從公司內部監控系統來看,測試前後的qps基本一致,保證了測試數據的可靠性。然後對比系統響應時間。系統主要的幾個接口響應時間基本降低了10-20ms,同時系統的整體平均響應時間降低了4%。說明了開啓緩存模式確實有助於減少系統的響應時間,提升系統的性能。

 

優化日誌完整性

       那麼問題來了:既然開啓緩存模式能提高系統,那麼log4j爲什麼沒有默認開啓緩存模式呢?個人猜測是因爲緩存模式需要緩存填充滿了纔會寫入磁盤,但是假如系統突然崩潰了,緩存中殘留的數據沒有寫入磁盤,從而導致日誌丟失。而系統崩潰時的日誌往往是排查和定位問題的關鍵,所以大部分情況下日誌的完整性更爲重要。但是享受緩慢模式的高性能與日誌的完整性真的是魚與熊掌不可兼得嗎?log4j的DailyRollingFileAppender確實不可兼得,但是可以自己擴展DailyRollingFileAppender類。使用jvm鉤子,在jvm退出前,先把緩存裏的日誌寫到磁盤上。下面是自己擴展的ExtendDailyRollingFileAppender。 

 

public class ExtendDailyRollingFileAppender extends DailyRollingFileAppender{ 
public ExtendDailyRollingFileAppender() {   
        super();   
        Runtime.getRuntime().addShutdownHook(new Log4jHockThread());   
    } 
public ExtendDailyRollingFileAppender(Layout layout, String filename,   
            String datePattern) throws IOException {   
        super(layout, filename, datePattern);   
        Runtime.getRuntime().addShutdownHook(new Log4jHockThread());   
    } 
public QuietWriter getQw() {   
        return super.qw;   
    }  
private class Log4jHockThread extends Thread {   
       @Override   
       public void run() {   
           QuietWriter qw = ExtendDailyRollingFileAppender.this.getQw();   
           if (qw != null) {   
               qw.flush();   
           }   
       }   
   } 
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章