現狀
加參數 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/test.dump
可以實現在jvm
發生內存錯誤后 會生成dump文件 方便開發人員分析異常原因。
當運行在k8s中,如果進程發生錯誤 導出dump文件后 ,k8s會重啟dokcer容器,上一次崩潰生成的dump文件就沒有了。如果應用並沒有完全崩潰 此時極其不穩定 最好也能通知到技術人員來處理。這樣不方便我們排查原因 所有寫了一個小工具。大概原理如下
1、 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/test.dump
當發生內存錯誤的時候 導出堆文件
2、 -XX:OnOutOfMemoryError=./dumpError.sh
當發生內存溢出的時候,讓JVM調用一個shell腳本 這個shell腳本可以做一些資源整理操作 比如kill掉當前進程並重啟
依賴上面2點jvm
特性 就能做到把dump文件收集起來 是通知技術人員也好(比如發送訂單、短信報警等)、然后再把dump文件上傳到OSS
或者其他的文件存儲中。 需要值得注意的是-XX:OnOutOfMemoryError=xx.sh
執行的腳本不能傳腳本參數,所以盡可能把參數都封裝在另一個腳本中。
方案實現
基於Go
簡單的寫了一個上傳阿里OSS的方法 這里用其他任何語言都可以的,至於用GO的原因很簡單,有第三方庫可以調用、運行的機器上也不用安裝sdk、比較輕量。
大致邏輯如下
jvmdump.go
init獲取程序的輸入參數
func init(){
fmt.Println("init....")
flag.StringVar(&env, "env", "test", "test") //用於區分環境
flag.StringVar(&ddtoken, "ddtoken", "", "ddtoken") //用於報警用的 釘釘機器人TOKEN
flag.StringVar(&dumpFile, "dfile", "", "dfile") // dump文件的地址
flag.StringVar(&pod, "pod", "", "pod") //k8s中的pod 只是記錄一下 方便排查
}
main函數邏輯
func main() {
fmt.Println("start invoke dump...")
flag.Parse() //解析輸入參數
fmt.Printf("dumpFile %s ,env %s token %s\n",dumpFile,env,ddtoken)
exist, err := FileExists(dumpFile) //驗證dump文件是否存在 只有存在的時候才去處理收集dump文件邏輯
if err != nil {
fmt.Printf("驗證文件是否存在發生錯誤![%v]\n", err)
return
}
if exist {
//https://help.aliyun.com/document_detail/88604.html
var url=uploadOSS(dumpFile) //上傳阿里oss
fmt.Printf("OSS上傳完成 %s\n", url)
if enabledd{
//釘釘群機器人發送工具 https://github.com/braumye/grobot
notifyDD(url) //通知釘釘群機器人
}
}else{
fmt.Printf("dump文件不存在 %s\n",dumpFile)
}
}
構建可執行文件
set GOOS=linux
go build -ldflags "-w -s"
測試 驗證go腳本是否正確
echo "ffff">/opt/ttt.dump
./jvmdump -env test -dfile /opt/ttt.dump
如果能成功上傳 就可以集成到jvm上跑了,不能成功上傳的話 就需要調一下go了。
另外分享一個-XX:OnOutOfMemoryError=./dumpError.sh
參考。
有這個shell的原因是因為 由於jvm
中OnOutOfMemoryError
目前沒有找到可以傳遞腳本參數的方法。 所有不能調用./jvmdump
文件 故包裝一下,把參數都封裝在dempError.sh中 ,把所有生成的dump文件 后綴命名都設置為.dump,主要是為了方便查找。放在一個獨立的目錄也是可以的。
dumpError.sh
#!/bin/bash
#循環目錄
traverse_dir()
{
filepath=$1
for file in `ls -a $filepath`
do
if [ -d ${filepath}/$file ]
then
if [[ $file != '.' && $file != '..' ]]
then
#遞歸
traverse_dir ${filepath}/$file
fi
else
#調用查找指定后綴文件
check_suffix ${filepath}/$file
fi
done
#看需要 可以kill掉進程,避免jvm沒有完全崩潰 k8s不會重啟pod的情況 造成應用假死問題。
}
#查找指定后綴的文件 這里在k8s環境里一般只會有一個dump文件,如果可能存在多個的dump文件文件的情況 可能需要變更一下邏輯
check_suffix()
{
file=$1
#如果找到dump就調用go寫的jvmdump腳本
if [ "${file##*.}"x = "dump"x ];then
lib/jvmdump -e test -dfile $file -pod $HOSTNAME -ddtoken xxx
fi
}
traverse_dir /opt/logs
完整代碼參考