目錄java
一個成功的java項目標準並不只僅是業務功能實現,可是縱觀國內,不少項目組在前期項目開發設計中只考慮了業務功能,沒有考慮項目後期維護的監控設計。沒有完善的監控運維設計,項目存活的壽命應該也不長吧?好的項目可以吸引人留下來,並不斷強化項目的功能優化每一處代碼,壞的項目只會逼死人,不斷的增長齷齪代碼以致於根本沒法維護。 固然從公司來講,業務的首要實現是公司可以賺錢的有效保障,公司賺不了錢了,寫的在好的代碼也只能靜靜的躺在硬盤中。我想一個負責的開發人員不只要能重視業務功能的實現,還能保證在項目上線運維中針對突發狀況作到監控。
我理解的監控分兩種,一種是運維的監控-監控整個集羣的各項資源的使用狀況以及各個服務的存活狀況,另外一種是開發的監控-監控代碼問題致使的線程死鎖,OOM等,以及業務消息的歷史可回溯。
我是一名開放,這裏主要講講個人心得,開發中的監控。如何減小開發人員沒必要要的加班。linux
應用代碼在面對線上各類請求時,常常會發生死鎖,OOM等問題。這個時候咱們如何去查看呢? 若是咱們不想連上遠程服務器,經過本地的一些可視化工具鏈接遠程程序,查看遠程程序的線程,CPU,GC,堆內存等使用狀況。
這裏只是演示JMX的監控功能,JMX還有動態修改bean屬性等功能不在這一篇文章講解。
修改密碼,找到配置文件$JAVA_HOME/jre/lib/management/jmxremote.password.template,複製一份並更名爲jmxremote.password,而後修改只讀權限並編輯jmxremote.passwrod,取消如下兩行註釋:centos
#monitorRole QED #controlRole R&D
打開tomcat的bin目錄下的catalina.sh,加入如下內容**(非tomcat程序也相似)**
JAVA_OPTS="$JAVA_OPTS -Djava.rmi.server.hostname=192.168.19.131 -Dcom.sun.management.jmxremote.port=18999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
參數authenticate表示是否須要密碼認證,賦值爲true就會使用jmxremote.password設置的密碼。緩存
監控的程序是由哪一個用戶啓動,則把jmxremote.password文件的權限改成這個用戶的只讀權限,不然啓動程序會報錯:Error: Password file read access must be restricted。這些在jmxremote.password裏的註釋都有說明。好比,若是你是用intsmaze用戶啓動java程序
chown intsmaze jmxremote.password chmod 400 jmxremote.password
先啓動待監控的程序tomcat
sh startup.sh
左邊欄,右鍵「遠程」>>「添加遠程主機」
安全
左側欄,右鍵剛纔添加的遠程主機>>「添加jmx連接」,使用配置的端口
若是咱們不配置JVM_OPTS參數,那麼咱們在本地使用javaVisualVM是沒法訪問遠程服務器上的tomcat服務的情況,要想知道遠程服務器的情況就必須使用CRT等工具連上服務器使用linux命令去查看程序的運行狀況。服務器
在java -cp 命令中加入以下參數便可架構
java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=22222 -cp jmx.jar cn.intsmaze.thread.TestDeadThread
TestDeadThread類以下運維
public class TestDeadThread implements Runnable { int a, b; public TestDeadThread(int a, int b) { this.a = a; this.b = b; } public void run() { synchronized (Integer.valueOf(a)) { synchronized (Integer.valueOf(b)) { System.out.println(a + b); } } } public static void main(String[] args) throws InterruptedException { Thread.sleep(3000); for (int i = 0; i < 100; i++) { new Thread(new TestDeadThread(1, 2)).start(); new Thread(new TestDeadThread(2, 1)).start(); } } }
JvisiualVM經過JMX的方式鏈接到遠程服務器上的JVM,此時能獲取到JVM的基本信息(啓動參數、系統屬性)、CPU使用狀況、堆內存總體狀況以及線程的總體狀況等。但若是想經過Visual GC插件進一步瞭解堆內各區的狀況的話,就會發現插件此時並不工做。
Visual GC插件不工做,是由於此插件使用的協議是RMI,所以須要使用下面的jstatd方式進行鏈接。jvm
JVM jstat Daemon:守護進程,一個RMI服務器程序,用於監控本地全部JVM從建立開始直到銷燬整個過程當中的資源使用狀況,同時提供接口給監控工具(如這裏的VisualVM),讓工具能鏈接到本機全部的JVM。
${java_home}/bin目錄下啓動jstatd服務
[intsmaze@centos-Reall-131 bin]./jstatd Could not create remote object access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write") java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write") at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472) at java.security.AccessController.checkPermission(AccessController.java:884) at java.lang.SecurityManager.checkPermission(SecurityManager.java:549) at java.lang.System.setProperty(System.java:792) at sun.tools.jstatd.Jstatd.main(Jstatd.java:139)
因爲jstatd server沒有提供任何對遠程client端的認證,客戶端程序獲取到本地當前用戶的全部JVM信息後可能存在安全隱患,因此jstatd要求啓動以前必須指定本地安全策略,不然jstatd進程沒法啓動,拋出上面錯誤。
在須要被監控的遠程主機建立一個安全策略文件,好比保存爲/home/intsmaze/jdk1.8.0_144/bin/jstatd-all.policy,內容以下:
grant codebase "file:/home/intsmaze/jdk1.8.0_144/lib/tools.jar" { permission java.security.AllPermission; };
經過以下命令能夠成功啓動jstatd server
./jstatd -J-Djava.security.policy=/home/intsmaze/jdk1.8.0_144/bin/jstatd-all.policy -J-Djava.rmi.server.logCalls=true ./jstatd -J-Djava.security.policy=/home/intsmaze/jdk1.8.0_144/bin/jstatd-all.policy &
向經過jstatd命令啓動的JVM(Main class:sun.tools.jstatd.Jstatd)傳遞參數,好比-J-Xms48m指定了Jstatd這個JVM的初始堆內存爲48MB
右鍵選擇創建jstatd鏈接
對應的遠程主機節點下會自動列出全部運行的JVM
JMX:使用JMX須要遠程JVM在啓動的時候開啓遠程訪問支持,設定JMX端口等,每個JMX鏈接一個遠程JVM。
JStatD:使用jstatd鏈接方式時,須要在遠程主機上建立安全策略文件而後啓動jstatd進程,而且此進程須要一直保持運行狀態,客戶端能夠看到遠程主機上當前用戶的全部JVM的信息,即只要建立一個jstatd鏈接。
若是咱們不配置JMX和jstatd,那麼咱們沒法使用jvisiualVM去監控遠程JVM程序,要知道程序的運行狀態咱們必須連上服務器去查看。
[intsmaze@centos-Reall-131 ~]$ top top - 13:04:07 up 3 min, 2 users, load average: 0.00, 0.01, 0.00 Tasks: 104 total, 1 running, 103 sleeping, 0 stopped, 0 zombie Cpu(s): 0.0%us, 0.2%sy, 0.0%ni, 99.8%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Mem: 2086348k total, 224720k used, 1861628k free, 37484k buffers Swap: 2064376k total, 0k used, 2064376k free, 91204k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 385 root 20 0 0 0 0 S 0.3 0.0 0:00.02 flush-8:0 2211 intsmaze 20 0 858m 25m 9448 S 0.3 1.2 0:00.87 java
第一行:load average: 0.41, 0.45, 0.43 系統負載,即任務隊列的平均長度。1分鐘前、5分鐘前、15分鐘前平均負載
第二行:Tasks: 141 total 進程總數,0 zombie 殭屍進程數
第三行爲cpu信息
6.1% us 用戶空間佔用CPU百分比
1.5% sy 內核空間佔用CPU百分比
0.0% ni 用戶進程空間內改變過優先級的進程佔用CPU百分比
92.2% id 空閒CPU百分比
0.0% wa 等待輸入輸出的CPU時間百分比
0.0% hi 硬件中斷
0.0% si 軟件中斷
0.0%st 實時
第4、五行爲內存信息。
Mem: 191272k total 物理內存總量
22052k buffers 用做內核緩存的內存量
Swap: 192772k total 交換區總量
123988k cached 緩衝的交換區總量
[intsmaze@centos-Reall-131 ~]$ top -Hp 2461 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2462 intsmaze 20 0 870m 25m 9416 S 30.0 1.2 0:00.28 java 2463 intsmaze 20 0 870m 25m 9416 S 0.0 1.2 0:00.00 java 2464 intsmaze 20 0 870m 25m 9416 S 0.0 1.2 0:00.00 java
Jstack是JDK自帶的命令行工具,主要用於線程Dump分析,能獲得運行java程序的java stack和native stack的信息,能夠輕鬆得知當前線程的運行狀況。
jstack -l 2238 > intsmaze.log [intsmaze@centos-Reall-131 ~]$ jstack -l 2461 "Thread-200": at cn.intsmaze.thread.TestDeadThread.run(TestDeadThread.java:29) - waiting to lock <0x9d62a3a0> (a java.lang.Integer) at java.lang.Thread.run(Thread.java:748) "Thread-10": at cn.intsmaze.thread.TestDeadThread.run(TestDeadThread.java:30) - waiting to lock <0x9d62a390> (a java.lang.Integer) - locked <0x9d62a3a0> (a java.lang.Integer) at java.lang.Thread.run(Thread.java:748)
jstack命令生成的thread dump信息包含了JVM中全部存活的線程,爲了分析指定線程,必須找出對應線程的調用棧,應該如何找?
top -Hp [pid] 中獲取到了佔用cpu資源較高的線程pid,將該pid轉成16進制的值,在thread dump中每一個線程都有一個nid,找到對應的nid便可。
獲得2462 的十六進制值 ··· [intsmaze@centos-Reall-131 ~]$ printf "%x\n" 2462 99e ··· jstack -l 21711 | grep 99e "PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x99e in Object.wait()
在nid=0x99e 的線程調用棧中,CPU消耗在PollIntervalRetrySchedulerThread這個類的Object.wait(),而後去觀察本身寫的業務代碼。
當初小弟運氣好,作了一個比較核心的紅包業務,基本上每週都會有新的版本發佈。並且面對的人羣是普通用戶,用戶一發現消費沒有中紅包,就會打客服,而後我這邊就會收到反饋,這個時候就要根據客戶的交易id查詢緣由給出反饋。若是當初在開發的時候,沒有考慮到源碼插樁,那麼這個時候我就會頭疼,推出去的報文相應字段確實沒有中紅包,而後我去看規則是不是這筆交易沒有知足,而後找了幾天仍是沒有給出讓人信服的答案。在這個系統架構師對咱們全部的系統作了源碼插樁,一條記錄從進入系統,走過那些條件判斷的流程,每個條件判斷的值都進行了插樁,而後匯聚成一條消息處理記錄存儲在hbase。而後面對這種狀況,咱們只須要去hbase中查詢一下,拿出這條消息在整個系統的路徑情況變一目瞭然了。