調試大規模服務器集群的五大策略

jopen 9年前發布 | 15K 次閱讀 集群 集群/負載均衡

原文  http://www.csdn.net/article/2015-01-12/2823518-debugging-production-server

【編者按】隨著互聯網迅速發展,用戶訪問量以及服務器規模的越來越大,因此,創建一個可靠、穩定、優質的互聯網服務是開發者的首要目標。而對于開發者而 言,是否具備一個完善的服務器調試策略將對整個部署維護工作有著至關重要的影響。作者Alex Zhitnitsky現就職于 Takipi ,其經常幫助Java、Scala開發人員解決服務器端的錯誤和對常用軟件進行測試。本文是Alex分享的一些經驗。

譯文如下:

對運行中的真實環境進行調試,比在 IDE中進行要困難很多。斷點,單步執行等都會變得非常奢侈。因此要做的第一件事是要做出周密的調試計劃,否則漫無目的單純依靠日志記錄的做法,將是非常低效的。 其次,規模越大的架構會更容易出現差錯。因此,如何篩查出錯誤源頭,明確哪個步驟出錯是非常重要的。

一、分布式日志

對于每條記錄,我們需要認真分析并了解其背后的含義。但是對于龐大的日志記錄我們需要高效的方法來處理,具體請參考這篇文章 Logback 調節的7 個方法

調試大規模服務器集群的五大策略

什么樣的記錄是真正需要的?

答案是全部!因為代碼會影響到整個應用的方方面面。此外,事務 ID也是很重要的。它能有助于處理異常,因為事務ID經常會貫穿于 節點、進程、線程之間。一個較好的處理方法是在App的每個線程入口生成一個UUID。然后把該ID附加到日志記錄中,進行全程監視。該方法在分布式和異 步日志中起著舉足輕重的作用,特別是與日志管理工具如Logstash和Loggly等一起使用時。

異常處理

未知異常很容易會導致系統崩潰。所以建議在代碼末端設置一個全局異常處理句柄,例如在 Java中進行下面的代碼編寫:

public static void Thread.setDefaultUncaughtExceptionHandler(
  UncaughtExceptionHandler eh);
void uncaughtException( Thread t, Throwable e) {
  logger.error(“Uncaught error in thread “ + t.getName(), e);
}

這或許看起來與 Tomcat或Akka框架有點類似。最后這里給出三種處理未知異常時的方法:

1. 線程名:根據需要處理的請求來變更線程名是個巧妙的方法。例如在事務處理的任何時間,把事務 ID先附加到線程,然后在結束時移除掉。

2. 本地線程存儲 (Thread-local storage,TLS):這是一種使線程特定數據從線程對象分離的方法。借助這些特定數據能便于對出現的錯誤進行排查。例如事務ID,時間或用戶名。否則在欠缺這些數據和線程名的情況下,我們將不得不花費更多時間來處理未知異常。

3. 線程映射表 (Mapped Diagnostic Context,MDC):MDC類似于本地線程概念,是日志框架的一部分如Log4j或Logback。它在日志級別生成了一個靜態映射表,能夠較TLS實現更多高級特性。

二、快人一步的Jstack

Jstack對 Java開發者來說并不陌生,這是一款強大的JDK工具。簡單來說,Jstack能夠進入一個正在運行的進程然后輸出所有的線程meta信息,例如堆跟蹤,框架,鎖等等。此外它能夠對已銷毀的進程進行heap dumps或core dumps分析。

不過很多時候 Jstack是用在回顧的環節,如果錯誤已經發生,它反饋的可能是過時的信息。因此如何更主動地使用Jstack是關鍵所在。例如,設置一個吞吐量閥值然后在該值下降時啟動jstack。

public void startScheduleTask() {
  scheduler.scheduleAtFixedRate(new Runnable() {
     public void run() {
        checkThroughput();
     }
  }, APP_WARMUP, POLLING_CYCLE, TimeUnit.SECONDS);
}
private void checkThroughput()
{
  int throughput = adder.intValue(); //the adder is inc’d when a message is processed
  if (throughput < MIN_THROUGHPUT) {
     Thread.currentThread().setName("Throughput jstack thread: " + throughput);
     System.err.println("Minimal throughput failed: executing jstack");
     executeJstack();  // See the code on GitHub to learn how this is done
  }
  adder.reset();
}

三、 狀態 Jstack

Jstack應用時需要注意的另一個問題是由于它會返回非常多的線程 meta數據,如果缺乏相關的實際狀態數據,將會對錯誤排查造成不便。以數據庫查詢為例子,可以加上如下一行代碼:

Thread.currentThread().setName(Context + TID + Params + current Time, ...);

我們來比較加入前后的數據輸出:

加入前:

pool-1-thread-1 #17 prio=5 os_prio=31 tid=0x00007f9d620c9800 nid=0x6d03 in Object.wait() [0x000000013ebcc000]

加入后:

”Queue Processing Thread, MessageID: AB5CAD, type:  AnalyzeGraph, queue: ACTIVE_PROD, Transaction_ID: 5678956,  Start Time: 10/8/2014 18:34″  #17 prio=5 os_prio=31 tid=0x00007f9d620c9800 nid=0x6d03 in Object.wait() [0x000000013ebcc000]

不難看出,加入代碼后的信息輸出顯得更加清晰了。例如線程正在做什么,接收了什么參數如事務ID和消息ID。這些對后續的回滾,錯誤重現、分離等步驟都是很有幫助的。

四、 開源追蹤工具BTrace

如果在不依靠日志和改變代碼的前提下,如何去追蹤運行時 JVM狀態呢?答案是BTrace Java代理。在添加該代理后,可使用BTrace腳本語言來獲取相關信息。

例如以下腳本:

@BTrace public class Classload {
   @OnMethod(
  Clazz=”+java.lang.ClassLoader”,
  method=”defineClass”,
  location=@Location(Kind.RETURN)
    )
    public static void defineClass(@Return class cl) {
  println(Strings.strcat(“loaded ”, Reflective.name(cl)));
  Threads.jstack();
      println(“==============================”);
   }
}

上述代碼對全部 ClassLoaders及其子類進行跟蹤,當defineClass返回時,該腳本會列出載入的類并啟動 JStack。但是我們不建議在實際環境中長期使用BTrace。因為Java代理會造成一定的資源開銷,同時需要編寫不同的腳本來進行追蹤。不過在想避 免重啟JVM的情況下在運行時環境修改跟蹤腳本,BTrace是個不錯的選擇。

五、自定義JVM代理

在不改動服務器代碼的前提下進行調試, JVM代理是最佳選擇。類似于BTrace,我們可以嘗試編寫自定義Java代理。這種代 理可以進入對象結構體然后在對象實例化的時候進行堆追蹤。然后我們可以對結果進行分析并掌握具體的載入過程。這是BTrace所不具備的,因為 BTrace有限制和只能進行讀操作。請看下面的示例代碼:

private static void internalPremain(String agentArgs, Instrumentation inst) throws IOException {

….

   Transformer transformer = new Transformer(targetClassName);

   inst.addTransformer(transformer, true); // the true flag let’s the agent hotswap running classes

}

這里創建了一個 transformer對象,并注冊到一個能對類進行變更的對象之上。完整代碼請點擊 這里 進行查看。

調試大規模服務器集群的五大策略

小結

綜上所述,獲得的有價值數據越多,解決問題的速度就越快。在當今信息為王的時代,宕機時間的影響幾以秒計,因此是否具備一個完善的服務器調試策略將對整個部署維護工作有著至關重要的影響。

文章出自: Highscalability

(責編/夏夢竹)

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!