對(duì)運(yùn)行中的真實(shí)環(huán)境進(jìn)行調(diào)試,比在IDE中進(jìn)行要困難很多。斷點(diǎn),單步執(zhí)行等都會(huì)變得非常奢侈。因此要做的第一件事是要做出周密的調(diào)試計(jì)劃,否則漫無(wú)目的單純依靠日志記錄的做法,將是非常低效的。其次,規(guī)模越大的架構(gòu)會(huì)更容易出現(xiàn)差錯(cuò)。因此,如何篩查出錯(cuò)誤源頭,明確哪個(gè)步驟出錯(cuò)是非常重要的。
一、分布式日志
對(duì)于每條記錄,我們需要認(rèn)真分析并了解其背后的含義。但是對(duì)于龐大的日志記錄我們需要高效的方法來(lái)處理。
什么樣的記錄是真正需要的?
答案是全部!因?yàn)榇a會(huì)影響到整個(gè)應(yīng)用的方方面面。此外,事務(wù)ID也是很重要的。它能有助于處理異常,因?yàn)槭聞?wù)ID經(jīng)常會(huì)貫穿于節(jié)點(diǎn)、進(jìn)程、線程之間。一個(gè)較好的處理方法是在App的每個(gè)線程入口生成一個(gè)UUID。然后把該ID附加到日志記錄中,進(jìn)行全程監(jiān)視。該方法在分布式和異步日志中起著舉足輕重的作用,特別是與日志管理工具如Logstash和Loggly等一起使用時(shí)。
異常處理
未知異常很容易會(huì)導(dǎo)致系統(tǒng)崩潰。所以建議在代碼末端設(shè)置一個(gè)全局異常處理句柄,例如在Java中進(jìn)行下面的代碼編寫(xiě):
[js] view plaincopy在CODE上查看代碼片派生到我的代碼片
public static void Thread.setDefaultUncaughtExceptionHandler(
UncaughtExceptionHandler eh);
void uncaughtException( Thread t, Throwable e) {
logger.error(“Uncaught error in thread “ + t.getName(), e);
}
這或許看起來(lái)與Tomcat或Akka框架有點(diǎn)類似。最后這里給出三種處理未知異常時(shí)的方法:
1. 線程名:根據(jù)需要處理的請(qǐng)求來(lái)變更線程名是個(gè)巧妙的方法。例如在事務(wù)處理的任何時(shí)間,把事務(wù)ID先附加到線程,然后在結(jié)束時(shí)移除掉。
2. 本地線程存儲(chǔ)(Thread-local storage,TLS):這是一種使線程特定數(shù)據(jù)從線程對(duì)象分離的方法。借助這些特定數(shù)據(jù)能便于對(duì)出現(xiàn)的錯(cuò)誤進(jìn)行排查。例如事務(wù)ID,時(shí)間或用戶名。否則在欠缺這些數(shù)據(jù)和線程名的情況下,我們將不得不花費(fèi)更多時(shí)間來(lái)處理未知異常。
3. 線程映射表(Mapped Diagnostic Context,MDC):MDC類似于本地線程概念,是日志框架的一部分如Log4j或Logback。它在日志級(jí)別生成了一個(gè)靜態(tài)映射表,能夠較TLS實(shí)現(xiàn)更多高級(jí)特性。
二、快人一步的Jstack
Jstack對(duì)Java開(kāi)發(fā)者來(lái)說(shuō)并不陌生,這是一款強(qiáng)大的JDK工具。簡(jiǎn)單來(lái)說(shuō),Jstack能夠進(jìn)入一個(gè)正在運(yùn)行的進(jìn)程然后輸出所有的線程meta信息,例如堆跟蹤,框架,鎖等等。此外它能夠?qū)σ唁N毀的進(jìn)程進(jìn)行heap dumps或core dumps分析。
不過(guò)很多時(shí)候Jstack是用在回顧的環(huán)節(jié),如果錯(cuò)誤已經(jīng)發(fā)生,它反饋的可能是過(guò)時(shí)的信息。因此如何更主動(dòng)地使用Jstack是關(guān)鍵所在。例如,設(shè)置一個(gè)吞吐量閥值然后在該值下降時(shí)啟動(dòng)jstack。
[js] view plaincopy在CODE上查看代碼片派生到我的代碼片
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();
}
三、 Stateful Jstack
Jstack應(yīng)用時(shí)需要注意的另一個(gè)問(wèn)題是由于它會(huì)返回非常多的線程meta數(shù)據(jù),如果缺乏相關(guān)的實(shí)際狀態(tài)數(shù)據(jù),將會(huì)對(duì)錯(cuò)誤排查造成不便。以數(shù)據(jù)庫(kù)查詢?yōu)槔樱梢约由先缦乱恍写a:
[js] view plaincopy在CODE上查看代碼片派生到我的代碼片
Thread.currentThread().setName(Context + TID + Params + current Time, ...);
我們來(lái)比較加入前后的數(shù)據(jù)輸出:
加入前:
“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]
不難看出,加入代碼后的信息輸出顯得更加清晰了。例如線程正在做什么,接收了什么參數(shù)如事務(wù)ID和消息ID。這些對(duì)后續(xù)的回滾,錯(cuò)誤重現(xiàn)、分離等步驟都是很有幫助的。
四、 開(kāi)源追蹤工具BTrace
如果在不依靠日志和改變代碼的前提下,如何去追蹤運(yùn)行時(shí)JVM狀態(tài)呢?答案是BTrace Java代理。在添加該代理后,可使用BTrace腳本語(yǔ)言來(lái)獲取相關(guān)信息。
例如以下腳本:
[js] view plaincopy在CODE上查看代碼片派生到我的代碼片
@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(“==============================”);
}
}
上述代碼對(duì)全部ClassLoaders及其子類進(jìn)行跟蹤,當(dāng)defineClass返回時(shí),該腳本會(huì)列出載入的類并啟動(dòng)JStack。但是我們不建議在實(shí)際環(huán)境中長(zhǎng)期使用BTrace。因?yàn)镴ava代理會(huì)造成一定的資源開(kāi)銷,同時(shí)需要編寫(xiě)不同的腳本來(lái)進(jìn)行追蹤。不過(guò)在想避免重啟JVM的情況下在運(yùn)行時(shí)環(huán)境修改跟蹤腳本,BTrace是個(gè)不錯(cuò)的選擇。
五、自定義JVM代理
在不改動(dòng)服務(wù)器代碼的前提下進(jìn)行調(diào)試,JVM代理是最佳選擇。類似于BTrace,我們可以嘗試編寫(xiě)自定義Java代理。這種代理可以進(jìn)入對(duì)象結(jié)構(gòu)體然后在對(duì)象實(shí)例化的時(shí)候進(jìn)行堆追蹤。然后我們可以對(duì)結(jié)果進(jìn)行分析并掌握具體的載入過(guò)程。這是BTrace所不具備的,因?yàn)锽Trace有限制和只能進(jìn)行讀操作。請(qǐng)看下面的示例代碼:
[js] view plaincopy在CODE上查看代碼片派生到我的代碼片
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
}
這里創(chuàng)建了一個(gè)transformer對(duì)象,并注冊(cè)到一個(gè)能對(duì)類進(jìn)行變更的對(duì)象之上。
小結(jié)
綜上所述,獲得的有價(jià)值數(shù)據(jù)越多,解決問(wèn)題的速度就越快。在當(dāng)今信息為王的時(shí)代,宕機(jī)時(shí)間的影響幾以秒計(jì),因此是否具備一個(gè)完善的服務(wù)器調(diào)試策略將對(duì)整個(gè)部署維護(hù)工作有著至關(guān)重要的影響。
核心關(guān)注:拓步ERP系統(tǒng)平臺(tái)是覆蓋了眾多的業(yè)務(wù)領(lǐng)域、行業(yè)應(yīng)用,蘊(yùn)涵了豐富的ERP管理思想,集成了ERP軟件業(yè)務(wù)管理理念,功能涉及供應(yīng)鏈、成本、制造、CRM、HR等眾多業(yè)務(wù)領(lǐng)域的管理,全面涵蓋了企業(yè)關(guān)注ERP管理系統(tǒng)的核心領(lǐng)域,是眾多中小企業(yè)信息化建設(shè)首選的ERP管理軟件信賴品牌。
轉(zhuǎn)載請(qǐng)注明出處:拓步ERP資訊網(wǎng)http://www.guhuozai8.cn/
本文標(biāo)題:調(diào)試大規(guī)模服務(wù)器集群的五大策略
本文網(wǎng)址:http://www.guhuozai8.cn/html/consultation/10839617677.html