增压的jstack:如何以100mph的速度调试服务器
使用jstack調(diào)試實(shí)時(shí)Java生產(chǎn)服務(wù)器的指南
jstack就像U2一樣-從時(shí)間的黎明就一直在我們身邊,我們似乎無法擺脫它 。 除了笑話,到目前為止,jstack是您調(diào)試軍用生產(chǎn)服務(wù)器中最方便的工具之一。 即便如此,我仍然認(rèn)為它在情況惡化時(shí)能夠?qū)⒛鷱幕鹬袚錅绲哪芰θ晕吹玫匠浞掷?#xff0c;因此我想分享一些方法,您可以在對(duì)抗生產(chǎn)錯(cuò)誤的戰(zhàn)爭(zhēng)中將其增壓為更強(qiáng)大的武器。
jstack的核心是一個(gè)超級(jí)簡(jiǎn)單的工具,它向您顯示在目標(biāo)JVM中運(yùn)行的所有Java線程的堆棧跟蹤。 只需通過一個(gè)pid將其指向JVM進(jìn)程即可獲得該時(shí)間點(diǎn)所有線程堆棧跟蹤的打印輸出。 這使您能夠回答“這個(gè)服務(wù)器在做什么?”這個(gè)古老的問題,并使您更進(jìn)一步地了解它為什么真正在做它。 關(guān)于jstack的最大優(yōu)點(diǎn)是它輕巧–它不會(huì)給JVM增加任何性能開銷,也不會(huì)更改其執(zhí)行狀態(tài)(與調(diào)試器或探查器不同)。
由于沒有什么是完美的 ,因此jstack有兩個(gè)明顯的缺點(diǎn)。 第一個(gè)是jstack除了調(diào)用堆棧外沒有為您提供任何變量狀態(tài),這意味著盡管您可能正在查看堆棧,但您不知道將其置于何處的狀態(tài)。 一個(gè)很好的例子是查看掛起的JVM,其中jstack會(huì)向您顯示大量線程正在執(zhí)行數(shù)據(jù)庫(kù)查詢或等待獲得連接。
這可能意味著某些查詢執(zhí)行時(shí)間太長(zhǎng),導(dǎo)致其他線程要么等待連接,要么被拒絕。 在這里,您真的想知道正在執(zhí)行的查詢(或查詢的參數(shù)是什么)會(huì)導(dǎo)致速度下降以及何時(shí)開始。 當(dāng)然,這只是一個(gè)例子,在很多情況下,其中某些線程被阻塞并降低了應(yīng)用程序的吞吐量。 但是不幸的是,使用jstack時(shí),因?yàn)槟鷽]有獲得任何變量狀態(tài),所以您無法真正確定應(yīng)歸咎于哪個(gè)線程。 還是可以嗎?
jstack的第二個(gè)缺點(diǎn)是它不是永遠(yuǎn)在線的工具。 這意味著,當(dāng)問題發(fā)生時(shí),您必須在那里-在生產(chǎn)中這是罕見的事件。 在不斷重啟VM的彈性環(huán)境中,這一點(diǎn)更為真實(shí)。
好的部分到了–讓我們看一下兩種技術(shù),它們可以幫助我們克服這兩個(gè)缺點(diǎn),并使一個(gè)好的工具真正變得很棒。
創(chuàng)建有狀態(tài)線程數(shù)據(jù)
第一個(gè)問題是如何向jstack打印輸出添加狀態(tài)? 答案簡(jiǎn)單而強(qiáng)大-線程名稱。 盡管許多人錯(cuò)誤地認(rèn)為線程名稱是不可變的,或者是操作系統(tǒng)確定的屬性,但實(shí)際上,每個(gè)線程都具有可變的且非常重要的特征。 這也是進(jìn)入您的jstack流的關(guān)鍵所在。
實(shí)際的應(yīng)用程序就像日志記錄一樣,一旦它通過諸如servlet,actor或Scheduler之類的入口點(diǎn)輸入代碼,就應(yīng)該控制線程名。 此時(shí),您需要將其名稱設(shè)置為一個(gè)有意義的值,該值可以幫助您了解執(zhí)行上下文和相關(guān)參數(shù),從而可以幫助您隔離事務(wù)及其內(nèi)容。
這很可能包括–
這些數(shù)據(jù)將意味著查看打印輸出(例如下面的打印輸出實(shí)際上并不能告訴我們?nèi)魏尉€程在做什么或?yàn)槭裁?#xff09;與提供信息的輸出之間的區(qū)別:
“ pool-1-thread-1”#17 prio = 5 os_prio = 31 tid = 0x00007f9d620c9800 nid = 0x6d03 in Object.wait()[0x000000013ebcc000]
將此與以下線程打印輸出進(jìn)行比較:
“隊(duì)列處理線程,MessageID:AB5CAD,類型:AnalyzeGraph,隊(duì)列:ACTIVE_PROD,Transaction_ID:5678956,開始時(shí)間:2014/10/8 18:34”
#17 prio = 5 os_prio = 31 tid = 0x00007f9d620c9800 nid = 0x6d03 in Object.wait()[0x000000013ebcc000]
您在這里看到的是對(duì)該線程實(shí)際作用的更全面的解釋。 您可以輕松地從AWS隊(duì)列中查看其出隊(duì)消息,它正在分析該消息,其類型,ID和事務(wù)ID。 最后,但并非最不重要–線程何時(shí)開始對(duì)其進(jìn)行處理。 這可以幫助您非常快速地將注意力集中在那些被卡住的線程上,并查看它們所處的狀態(tài)。從那以后,在本地進(jìn)行優(yōu)化和復(fù)制將變得更加容易。
這里的替代方案是要么希望日志文件中有數(shù)據(jù),而且能夠?qū)⑷罩局械臄?shù)據(jù)關(guān)聯(lián)到該確切線程。 另一個(gè)選擇是在生產(chǎn)環(huán)境中本地或遠(yuǎn)程連接調(diào)試器。 既不是很愉快又很費(fèi)時(shí)間。
在線程名稱中寫入此信息也有助于傳統(tǒng)日志記錄。 即使大多數(shù)日志記錄框架都提供了可以添加到日志中的基于線程的上下文,您也必須確保正確配置它。 使用線程名稱還可以確保您將在日志中擁有所需的所有數(shù)據(jù)。
注意:有些人可能會(huì)說不要修改或更改線程名稱。 根據(jù)我多年的個(gè)人經(jīng)驗(yàn)以及許多同事的經(jīng)驗(yàn),我對(duì)此非常信奉。
使jstack始終在線
使用jstack時(shí),我們面臨的第二個(gè)挑戰(zhàn)是,就像調(diào)試器一樣,它是您必須在發(fā)生問題時(shí)立即手動(dòng)捕獲損壞狀態(tài)的工具。 但是,當(dāng)服務(wù)器掛起或低于或低于某個(gè)特定閾值時(shí),有一種更活躍的方法可以使用jstack自動(dòng)生成打印輸出。 關(guān)鍵是要以編程方式調(diào)用jstack,就像在滿足特定應(yīng)用程序條件時(shí)從JVM中的任何日志記錄功能一樣。
這里的兩個(gè)主要挑戰(zhàn)是何時(shí)以及如何實(shí)現(xiàn)。
如何以編程方式激活jstack?
由于jstack是一個(gè)普通的OS進(jìn)程,因此調(diào)用它非常簡(jiǎn)單。 您要做的就是激活jstack進(jìn)程并將其指向您自己。 這里的關(guān)鍵是如何從JVM中獲取進(jìn)程的pid。 實(shí)際上,沒有標(biāo)準(zhǔn)的Java API可以做到這一點(diǎn)( 至少要等到Java 9才可以 )。 這是完成工作的一小段代碼(盡管它不是已記錄的api的一部分):
String mxName = ManagementFactory.getRuntimeMXBean().getName();int index = mxName.indexOf(PID_SEPERATOR);String result;if (index != -1) {result = mxName.substring(0, index); } else {throw new IllegalStateException("Could not acquire pid using " + mxName); }另一個(gè)小挑戰(zhàn)是將jstack輸出定向到您的日志中。 使用輸出流檢流器也很容易設(shè)置。 在此處查看有關(guān)如何將您調(diào)用的進(jìn)程輸出的輸出數(shù)據(jù)定向到日志文件或輸出流中的示例。
盡管可以使用getAllStackTraces在內(nèi)部捕獲正在運(yùn)行的線程的堆棧跟蹤,但出于多種原因,我更喜歡通過運(yùn)行jstack來執(zhí)行此操作。 首先,這是我通常希望在正在運(yùn)行的應(yīng)用程序外部進(jìn)行的操作(即使JVM正在參與提供信息),以確保我不會(huì)通過內(nèi)省調(diào)用來影響應(yīng)用程序的穩(wěn)定性。 另一個(gè)原因是,jstack在功能方面更為強(qiáng)大,例如向您顯示本機(jī)框架和鎖定狀態(tài),而這在JVM中是不可用的。
什么時(shí)候激活jstack?
您需要做出的第二個(gè)決定是在什么條件下您希望JVM記錄jstack。 當(dāng)服務(wù)器低于或高于特定處理(即請(qǐng)求或消息處理)閾值時(shí),可能會(huì)在預(yù)熱期之后執(zhí)行此操作。 您可能還需要確保每次激活之間都花費(fèi)足夠的時(shí)間。 只是為了確保您不會(huì)在低負(fù)載或高負(fù)載下泛濫日志。
您將在此處使用的模式是從JVM內(nèi)加載看門狗線程,該線程可以定期查看應(yīng)用程序的吞吐量狀態(tài)(例如,最近兩分鐘內(nèi)處理的消息數(shù)),并確定是否對(duì)屏幕進(jìn)行“截屏”。線程狀態(tài)將很有幫助,在這種情況下,它將激活jstack并將其記錄到文件中。
設(shè)置該線程的名稱以包含目標(biāo)吞吐量和實(shí)際吞吐量狀態(tài),因此當(dāng)您執(zhí)行自動(dòng)jstack快照時(shí),您可以確切地看到看門狗線程決定這樣做的原因。 由于這只會(huì)每隔幾分鐘發(fā)生一次,因此該過程沒有實(shí)際的性能開銷,尤其是與所提供數(shù)據(jù)的質(zhì)量相比。
下面的代碼片段顯示了這種模式的實(shí)際效果。 startScheduleTask加載看門狗線程以定期檢查吞吐量值,該吞吐量值在處理消息時(shí)使用Java 8 并發(fā)加法器遞增。
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 in inc’d when a message is processedif (throughput < MIN_THROUGHPUT) {Thread.currentThread().setName("Throughput jstack thread: " + throughput);System.err.println("Minimal throughput failed: exexuting jstack");executeJstack(); //see the code on github to see how this is done}adder.reset(); }- 在此處可以找到從代碼中搶占調(diào)用jstack的完整源代碼。
翻譯自: https://www.javacodegeeks.com/2014/10/supercharged-jstack-how-to-debug-your-servers-at-100mph.html
總結(jié)
以上是生活随笔為你收集整理的增压的jstack:如何以100mph的速度调试服务器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (那些地方遭受ddos)
- 下一篇: 使用JFace Viewer延迟获取模型