Java プロジェクトの成功の基準は、ビジネス機能の実現だけではありません。全国的に、多くのプロジェクト チームは、プロジェクトの開発と設計の初期段階でビジネス機能のみを考慮し、その後のプロジェクト保守の監視設計を考慮していません。完璧な監視運用・保守設計がなければプロジェクトの寿命は長くないはずですよね。良いプロジェクトは人々を惹きつけ、常にプロジェクトの機能を強化してあらゆるコードを最適化することができますが、悪いプロジェクトは人を殺すだけで、汚いコードが増え続けてまったく保守できなくなります。
もちろん、企業の観点からすると、ビジネスの主な実現は、企業が利益を上げられることを効果的に保証することですが、会社が利益を上げられなければ、よく書かれたコードはハードディスク上に静かに眠ることしかできません。責任ある開発者は、ビジネス機能の実現に注意を払うだけでなく、プロジェクトのオンライン運用と保守中に緊急事態を確実に監視する必要があると思います。
モニタリングは理解しました
私が理解している監視には2種類あり、1つはクラスタ全体の各種リソースの使用状況や各サービスの生存状況を監視する運用保守監視、もう1つはコードの問題によるスレッドのデッドロックやOOMなどを監視する開発監視で、ビジネスメッセージの履歴を遡ることができます。
私はオープンで、ここでは主に開発におけるモニタリングの経験について話します。開発者の不必要な残業を減らす方法。
コード例外の監視
应用代码在面对线上各种请求时,经常会发生死锁,OOM等问题。这个时候我们如何去查看呢?
如果我们不想连上远程服务器,通过本地的一些可视化工具连接远程程序,查看远程程序的线程,CPU,GC,堆内存等使用情况。
リモートホスト構成 jmx
这里只是演示JMX的监控功能,JMX还有动态修改bean属性等功能不在这一篇文章讲解。
パスワードを変更し、構成ファイル $JAVA_HOME/jre/lib/management/jmxremote.password.template を見つけて、コピーをコピーして jmxremote.password という名前に変更します。次に、読み取り専用権限を変更して jmxremote.passwrod を編集し、次の 2 行のコメントをキャンセルします。
#monitorRole QED
#controlRole R&D
起動するJavaプログラム起動パラメータ(JVM_OPTS)を変更します。
打开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
jvisualvmを起動する
監視対象のプログラムを最初に起動します
sh startup.sh
左側の列で、「リモート」を右クリック >> 「リモート ホストの追加」
左側の列で、追加したばかりのリモート ホストを右クリックし、[jmx リンクを追加] をクリックし、構成されたポートを使用します。
JVM_OPTS パラメータを設定しない場合、ローカルで javaVisualVM を使用してリモート サーバー上の Tomcat サービスにアクセスできません。リモート サーバーのステータスを知りたい場合は、CRT などのツールを使用してサーバーに接続し、Linux コマンドを使用してプログラムの実行ステータスを確認する必要があります。
サーバー上の Java プログラムを監視する
次のパラメータを 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 メソッドを使用する必要があります。
jstatd はリモート JVM に接続します
JVM jstat Daemon:守护进程,一个RMI服务器程序,用于监控本地所有JVM从创建开始直到销毁整个过程中的资源使用情况,同时提供接口给监控工具(如这里的VisualVM),让工具能连接到本机所有的JVM。
jstatdサービスを開始します
${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;
};
パラメータを指定して jjstatd を起動する
jstatd サーバーは次のコマンドで正常に起動できます。
./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接続とJStatD接続の違い
JMX: JMX を使用するには、リモート JVM で起動時のリモート アクセス サポートの有効化、JMX ポートの設定などが必要であり、各 JMX はリモート JVM に接続します。
JStatD: jstatd 接続メソッドを使用する場合、リモート ホストでセキュリティ ポリシー ファイルを作成し、jstatd プロセスを開始する必要があり、このプロセスは実行し続ける必要があります。クライアントは、リモート ホスト上の現在のユーザーのすべての JVM 情報を確認できます。つまり、jstatd 接続を作成するだけです。
linux命令监控jvm程序
如果我们不配置JMX和jstatd,那么我们无法使用jvisiualVM去监控远程JVM程序,要知道程序的运行状态我们必须连上服务器去查看。
top命令查看各进程CPU占用率
[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 实时 -
第四、五行为内存信息。
Mem: 191272k total 物理内存总量
22052k buffers 用作内核缓存的内存量
Swap: 192772k total 交换区总量
123988k cached 缓冲的交换区总量
进程中每个线程占用cpu情况
[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 [pid]查看所有线程信息
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中所有存活的线程,为了分析指定线程,必须找出对应线程的调用栈,应该如何找?
jstack -l [pid] | grep 16进制
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中查询一下,拿出这条消息在整个系统的路径状况变一目了然了。