目录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中查询一下,拿出这条消息在整个系统的路径情况变一目了然了。