小土刀

Linux 服务器检查指南

服务器有时候像一个爱哭爱闹的小宝宝,能听到它在哭,但如果不够熟悉的话,一时半会儿还真不知道到底是为啥而哭。本文结合自己工作中的一些经验,给出一些检查服务器运行状况的通用技巧。


注,章节名称源自英雄联盟中盲僧李青的技能。

更新历史:

  • 2016.09.28: 完成初稿

天音波/回音击

htop

最基本的招式就是 htop 了,基本囊括了各种信息(一般系统默认是 top

观察一段时间大概能对服务器的基本状态有一个认知,可以比较直观找出占用 CPU 和内存的应用。这里简单说明一下如何利用这些数据。

右上角的信息也可以通过 uptime 进行查看,比如:

wdxtub:~$ uptime
06:49:47 up 49 days, 2:29, 1 user, load average: 0.21, 0.13, 0.19

最后的三个数字表示 1 分钟、5 分钟和 15 分钟的平均负载,一般来说 0.7 是一个警戒线(对于单核来说)。从图中我们可以看到该服务器有 4 个核心,所以负载的数值超过 1 都是很正常的。

free

我们可以使用 free -m 来以 mb 为单位查看内存使用情况,如:

wdxtub:~$ free -m
total used free shared buffers cached
Mem: 16047 15871 176 0 217 9436
-/+ buffers/cache: 6217 9830
Swap: 0 0 0

虽然看起来 free 部分内存很少,但是第二行表示大部分内存是被缓存占用的,是 Linux 尽量利用内存的正常现象。这里只要注意 swap 的数值为 0 即可,否则频繁使用交换区充当内存,会明显降低系统性能。

金钟罩/铁布衫

dmesg

dmesg 输出系统日志,不过比较杂也比较难看懂

vmstat

vmstat 1 每行会输出一些系统核心指标(后面的数字表示每隔 1 秒输出一次)

wdxtub:~$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 341460 220212 9530820 0 0 10 436 1 1 8 1 86 5 0
0 0 0 341452 220212 9530820 0 0 0 10412 580 681 0 0 99 0 0
0 0 0 341420 220212 9530820 0 0 0 0 371 693 0 0 100 0 0
0 0 0 341420 220212 9530820 0 0 0 0 389 737 0 0 100 0 0
1 0 0 339320 220212 9532844 0 0 0 9020 9561 8007 15 4 70 11 0
0 0 0 335616 220212 9536784 0 0 0 0 1149 1295 6 1 93 0 0
0 0 0 339984 220212 9532188 0 0 0 5428 581 832 0 0 100 0 0

具体介绍一下几个特别需要关注的指标:

  • r 值,在等待 CPU 资源的进程数(不包含等待 IO 的进程),如果这个数值大于 CPU 的核心数目,就说明 CPU 资源已饱和
  • free 值,可用的内存大小(单位: KB)
  • siso 值,交换区写入和读取的数量,如果不为 0,则说明系统在使用 swap 空间,物理内存不足
  • us, sy, id, wa, st 值,表示用户时间(user),系统时间(system),空闲时间(idle),IO 等待时间(wait)和被偷走的时间(stolen,一般被其他虚拟机消耗)

如果用户时间和系统时间相加比较大,则 CPU 忙于执行指令,如果 IO 等待时间很长,那么系统的瓶颈在磁盘 IO。

pidstat

需要先安装 sudo apt install sysstat,然后使用 pidstat 1 可以查看进程的 CPU 占用率

wdxtub:~$ pidstat 3
Linux 3.13.0-91-generic 09/26/2016 _x86_64_ (4 CPU)
07:16:38 AM UID PID %usr %system %guest %CPU CPU Command
07:16:41 AM 0 7 0.00 0.33 0.00 0.33 2 rcu_sched
07:16:41 AM UID PID %usr %system %guest %CPU CPU Command
07:16:44 AM 1001 20573 0.00 0.33 0.00 0.33 2 pidstat
07:16:44 AM UID PID %usr %system %guest %CPU CPU Command
07:16:47 AM 0 10 0.00 0.33 0.00 0.33 1 rcuos/2
07:16:47 AM 0 386 0.00 0.33 0.00 0.33 1 jbd2/dm-0-8
07:16:47 AM 1001 13003 0.00 0.67 0.00 0.67 0 nginx
07:16:47 AM 1001 13004 0.00 0.67 0.00 0.67 1 nginx
07:16:47 AM 1001 13005 0.67 0.67 0.00 1.33 2 nginx
07:16:47 AM 1001 13006 0.67 0.00 0.00 0.67 3 nginx
07:16:47 AM 1001 22179 0.33 0.00 0.00 0.33 1 node
07:16:47 AM UID PID %usr %system %guest %CPU CPU Command
07:16:50 AM 1001 19021 0.33 0.33 0.00 0.67 1 sshd
07:16:50 AM 1001 20573 0.00 0.33 0.00 0.33 2 pidstat
07:16:50 AM UID PID %usr %system %guest %CPU CPU Command
07:16:53 AM 0 8 0.00 0.33 0.00 0.33 2 rcuos/0
07:16:53 AM 0 386 0.00 0.33 0.00 0.33 1 jbd2/dm-0-8
07:16:53 AM 1001 3408 76.33 8.33 0.00 84.67 3 java
07:16:53 AM 1001 13003 0.00 0.67 0.00 0.67 0 nginx
07:16:53 AM 1001 13005 0.67 0.67 0.00 1.33 2 nginx
07:16:53 AM 1001 13006 0.33 0.33 0.00 0.67 3 nginx
07:16:53 AM 1001 22179 0.00 0.33 0.00 0.33 0 node
07:16:53 AM UID PID %usr %system %guest %CPU CPU Command
07:16:56 AM 1001 3408 1.00 0.00 0.00 1.00 3 java
07:16:56 AM 1001 20573 0.00 0.33 0.00 0.33 2 pidstat
^C
Average: UID PID %usr %system %guest %CPU CPU Command
Average: 0 7 0.00 0.06 0.00 0.06 - rcu_sched
Average: 0 8 0.00 0.06 0.00 0.06 - rcuos/0
Average: 0 10 0.00 0.06 0.00 0.06 - rcuos/2
Average: 0 386 0.00 0.11 0.00 0.11 - jbd2/dm-0-8
Average: 1001 3408 12.89 1.39 0.00 14.28 - java
Average: 1001 13003 0.00 0.22 0.00 0.22 - nginx
Average: 1001 13004 0.00 0.11 0.00 0.11 - nginx
Average: 1001 13005 0.22 0.22 0.00 0.44 - nginx
Average: 1001 13006 0.17 0.06 0.00 0.22 - nginx
Average: 1001 19021 0.06 0.06 0.00 0.11 - sshd
Average: 1001 20573 0.00 0.17 0.00 0.17 - pidstat
Average: 1001 22179 0.06 0.06 0.00 0.11 - node

这个命令的好处是不会覆盖之前的数据,我们能够方便地观察系统动态。

iostat

可以使用 iostat -xz 1 来查看机器磁盘 IO 情况,如:

wdxtub:~$ iostat -xz 1
Linux 3.13.0-91-generic 09/26/2016 _x86_64_ (4 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
8.01 0.00 0.71 5.04 0.02 86.22
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
xvda 0.00 0.20 0.07 0.34 1.70 3.60 26.23 0.00 1.43 1.06 1.50 0.24 0.01
xvdb 0.00 158.04 1.76 47.14 28.13 1289.27 53.89 0.16 3.21 3.49 3.20 0.71 3.49
dm-0 0.00 0.00 2.48 228.71 37.46 1732.41 15.31 0.06 0.24 7.13 0.17 0.56 12.89
xvdf 0.00 1.19 0.72 22.34 9.33 443.14 39.24 0.58 24.94 15.99 25.23 4.28 9.87
avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 0.00 0.00 0.00 100.00
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
xvdb 0.00 143.00 0.00 6.00 0.00 596.00 198.67 0.00 0.00 0.00 0.00 0.00 0.00
dm-0 0.00 0.00 0.00 149.00 0.00 596.00 8.00 0.00 0.00 0.00 0.00 0.00 0.00
avg-cpu: %user %nice %system %iowait %steal %idle
0.25 0.00 0.00 0.00 0.00 99.75
  • r/s, w/s, rkB/s, wkB/s 表示每秒的读写次数和每秒读写的数据量,如果过大会引起性能问题
  • await 是 IO 操作的平均等待时间,如果数据异常增大,可能是硬件出了问题
  • avgqu-sz 是向设备发出的平均请求数量,大于 1 一般是硬件设备已饱和的表现
  • %util 是设备利用率,如果超过 60 就可能会影响 IO 性能

天雷破/摧筋断骨

sar

sar -n DEV 1 用来查看网络设备的吞利率,如

wdxtub@spro-usa-elasticsearch01:~$ sar -n DEV 1
Linux 3.13.0-91-generic (spro-usa-elasticsearch01) 09/26/2016 _x86_64_ (4 CPU)
07:49:06 AM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil
07:49:07 AM eth0 2.00 0.00 0.13 0.00 0.00 0.00 0.00 0.00
07:49:07 AM lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
07:49:10 AM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil
07:49:11 AM eth0 289.00 214.00 202.80 60.45 0.00 0.00 0.00 0.12
07:49:11 AM lo 206.00 206.00 180.29 180.29 0.00 0.00 0.00 0.00
07:49:15 AM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil
07:49:16 AM eth0 2364.00 1723.00 1488.95 459.65 0.00 0.00 0.00 0.87
07:49:16 AM lo 1771.00 1771.00 1101.98 1101.98 0.00 0.00 0.00 0.00
Average: IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil
Average: eth0 275.37 204.90 176.24 93.42 0.00 0.00 0.00 0.10
Average: lo 204.82 204.82 137.94 137.94 0.00 0.00 0.00 0.00

可以看到网络流量的波动特别大,但是远没有到达瓶颈,所以可能是其他的问题,需要继续排查。

sar -n TCP,ETCP 1 用来查看 TCP 连接的状态,如

wdxtub@spro-usa-elasticsearch01:~$ sar -n TCP,ETCP 1
Linux 3.13.0-91-generic (spro-usa-elasticsearch01) 09/26/2016 _x86_64_ (4 CPU)
07:52:50 AM active/s passive/s iseg/s oseg/s
07:52:51 AM 0.00 0.00 2.00 0.00
07:52:50 AM atmptf/s estres/s retrans/s isegerr/s orsts/s
07:52:51 AM 0.00 0.00 0.00 0.00 0.00
07:52:54 AM active/s passive/s iseg/s oseg/s
07:52:55 AM 176.00 339.00 4046.00 3443.00
07:52:54 AM atmptf/s estres/s retrans/s isegerr/s orsts/s
07:52:55 AM 0.00 0.00 0.00 0.00 0.00
07:52:57 AM active/s passive/s iseg/s oseg/s
07:52:58 AM 4.00 4.00 32.00 32.00
07:52:57 AM atmptf/s estres/s retrans/s isegerr/s orsts/s
07:52:58 AM 0.00 0.00 0.00 0.00 0.00
Average: active/s passive/s iseg/s oseg/s
Average: 21.48 41.25 495.45 422.05
Average: atmptf/s estres/s retrans/s isegerr/s orsts/s
Average: 0.00 0.00 0.00 0.00 0.00

其中:

  • active/s 是每秒本地发起的 TCP 连接数目,即 connect
  • passive/s 是每秒远程发起的 TCP 连接数目,即 accept
  • retrans/s 每秒 TCP 重传数量

猛龙摆尾

虽然现在各种框架和服务器基本已经帮我们处理好了 JVM 相关事宜,不过有些时候出了比较奇怪的问题,还是需要深入到 JVM 内部才能找到问题根源(最近我就经历了这样一次排查),下面是相关的一些命令指南。

jps

输出 JVM 中运行的进程状态信息,比如 jps -m -l,会输出传入 main 方法的参数和类的全名,如:

wdxtub@:~$ jps -m -l
21843 org.elasticsearch.bootstrap.Elasticsearch start
23012 sun.tools.jps.Jps -m -l

jstack

用来查看某个 Java 进程内的线程堆栈信息。比较常用的还是 -m-l 选项,其中 -m 会输出 Java 及 C/C++ 的堆栈信息,而 -l 是会打印出额外的锁信息。

我们可以 htop 查看进程信息,随手找一个 Java 应用,比方说 elasticsearch,对应的 pid 是 32346(不同机器不一样)。当然,也可以使用命令 ps -ef | grep elasticsearch | grep -v grep

然后我们可以看看这个进程内最吃 CPU 的线程,可以用的命令比较多,比如:ps -Lfp 32346, ps -mp 32346 -o THREAD,tid,time | sorttop -Hp 32346

我们这里使用 ps -mp 32346 -o THREAD,tid,time | sort,排序之后的结果是

wdxtub 0.4 19 - futex_ - - 571 00:05:31
wdxtub 0.6 19 - futex_ - - 32374 00:08:57
wdxtub 0.7 19 - futex_ - - 32452 00:10:48
wdxtub 0.7 19 - futex_ - - 32642 00:11:01
wdxtub 0.8 19 - futex_ - - 18940 00:07:26
wdxtub 0.8 19 - futex_ - - 32412 00:11:27
wdxtub 0.8 19 - futex_ - - 471 00:11:31
wdxtub 0.8 19 - futex_ - - 472 00:11:15
wdxtub 0.9 19 - futex_ - - 32650 00:13:03
wdxtub 28.5 - - - - - - 06:36:47
USER %CPU PRI SCNT WCHAN USER SYSTEM TID TIME

我们这里记录下最耗费时间的线程,编号为 32650,转换成十六进制为 0x7f8a,然后就可以查看最耗费时间的代码了,

wdxtub:~$ jstack 32346 | grep 7f8a
"elasticsearch[node-2][refresh][T#2]" #279 daemon prio=5 os_prio=0 tid=0x00007fd39447a000 nid=0x7f8a waiting on condition [0x00007fd147d2c000]

可以看到这里是在等待某个条件,不过因为 elasticsearch 的代码无法直接看到,这里就不能找找具体的原因了。

jmap 与 jhat

jmap 是 Java Memory Map,用来查看堆内存使用状况,而 jhat 是 Java Heap Analysis Tool,正好就是用来分析堆内存。这里我们尝试几个命令来看看效果:

jmap -heap pid 查看堆内存使用情况,包括使用的GC算法、堆配置参数和各代中堆内存使用情况,一个例子如下:

wdxtub:~$ jmap -heap 8183
Attaching to process ID 8183, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.102-b14
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 1073741824 (1024.0MB)
NewSize = 357564416 (341.0MB)
MaxNewSize = 357564416 (341.0MB)
OldSize = 716177408 (683.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 335544320 (320.0MB)
used = 334734664 (319.2278518676758MB)
free = 809656 (0.7721481323242188MB)
99.75870370864868% used
From Space:
capacity = 11010048 (10.5MB)
used = 8929984 (8.51629638671875MB)
free = 2080064 (1.98370361328125MB)
81.10758463541667% used
To Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
PS Old Generation
capacity = 716177408 (683.0MB)
used = 439459664 (419.1013946533203MB)
free = 276717744 (263.8986053466797MB)
61.361844019519815% used
20959 interned Strings occupying 2601056 bytes.

使用 jmap -histo[:live] pid 查看堆内存中的对象数目、大小统计直方图,如果带上 live 则只统计活对象,如下:

wdxtub:~$ jmap -histo:live 8183
num #instances #bytes class name
----------------------------------------------
1: 25679 47998512 [B
2: 66216 15910168 [C
3: 8707 2980024 [I
4: 62197 1492728 java.lang.String
5: 28749 919968 java.util.HashMap$Node
6: 17168 824064 org.apache.tomcat.util.buf.ByteChunk
7: 15363 737424 org.apache.tomcat.util.buf.CharChunk
8: 14563 699024 org.apache.tomcat.util.buf.MessageBytes
9: 6009 677840 java.lang.Class
10: 9152 608784 [Ljava.lang.Object;
11: 1976 497128 [Ljava.util.HashMap$Node;
12: 15396 492672 java.util.concurrent.ConcurrentHashMap$Node
13: 6690 380824 [Ljava.lang.String;
14: 4270 375760 java.lang.reflect.Method
15: 448 206128 [Ljava.util.concurrent.ConcurrentHashMap$Node;
16: 11699 187184 java.lang.Object
17: 2687 171968 java.net.URL
18: 2887 138576 java.util.HashMap
19: 4154 132928 java.util.Hashtable$Entry
20: 5077 121848 org.apache.tomcat.util.http.MimeHeaderField
21: 2923 116920 java.util.LinkedHashMap$Entry
22: 2492 99680 org.apache.catalina.loader.ResourceEntry
23: 678 97632 java.text.DecimalFormat
24: 1420 90880 java.util.concurrent.ConcurrentHashMap
25: 3703 88872 java.util.ArrayList
26: 1795 86160 java.nio.HeapByteBuffer
27: 2017 80680 java.lang.ref.SoftReference
28: 691 77392 java.util.GregorianCalendar
29: 1372 76832 java.util.LinkedHashMap
30: 2246 71872 java.lang.ref.WeakReference

其中 class name 有一些缩写,具体的意思是:

B byte
C char
D double
F float
I int
J long
Z boolean
[ 数组,如[I表示int[]
[L+类名 其他对象

当然,我们也可以把内存 dump 到文件中,再用 jhat 查看,比如 jmap -dump:format=b,file=dumpFileName 8183,dump 出来的文件可以使用 jhat 查看,如果 dump 文件太大,就可以加上 -J-Xmx512m 指定最大内存,比如 jhat -J-Xmx512m -port 9998 /tmp/dump.dat,然后就可以输入 主机地址:9998 来查看了

jstat

这个命令主要用来统计 JVM 信息,必须要指定的是虚拟机 ID,一般就是进程号,比方说 jstat -gc 8183 500 5 就是指 500ms 采样一次,采样数字为 5,比如:

wdxtub:~$ jstat -gc 8183 500 5
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
10752.0 10752.0 8881.2 0.0 327680.0 2318.5 699392.0 79410.9 36992.0 35946.6 3968.0 3721.2 5567 54.352 6 0.711 55.063
10752.0 10752.0 8881.2 0.0 327680.0 5282.0 699392.0 79410.9 36992.0 35946.6 3968.0 3721.2 5567 54.352 6 0.711 55.063
10752.0 10752.0 8881.2 0.0 327680.0 7929.1 699392.0 79410.9 36992.0 35946.6 3968.0 3721.2 5567 54.352 6 0.711 55.063
10752.0 10752.0 8881.2 0.0 327680.0 9310.1 699392.0 79410.9 36992.0 35946.6 3968.0 3721.2 5567 54.352 6 0.711 55.063
10752.0 10752.0 8881.2 0.0 327680.0 11648.8 699392.0 79410.9 36992.0 35946.6 3968.0 3721.2 5567 54.352 6 0.711 55.063

这里需要先讲解一下 JVM 堆内存的设计

简单来说,JVM 的堆分成三代:年轻代、年老代和永久代,年轻代里又分为 Eden 区以及 Suvivor 的 From 和 To 区,在 Survivor 区的对象如果经过多次 GC 还存活的话,会转移到年老区。参考链接中有一个具体的例子,感兴趣的同学可以前往查看。

简单来说,年轻代使用复制算法和标记-清除垃圾收集算法,永久代使用标记-整理算法进行垃圾回收算法,而年老代使用标记-整理垃圾回收算法,这里需要注意的是对年老代的的垃圾回收会停止所有事情(Stop The World),如果应用需要高实时,那么就要尽量避免年老代的垃圾回收(也就是 Full GC 了),否则可能整个服务器十多分钟没有响应。

具体调优的经验和规则可以参考这里,本文不深入了。

现在可以来介绍前面各个字段的意思了:

  • S0C、S1C、S0U、S1U:Survivor 0/1 区容量(Capacity)和使用量(Used)
  • EC、EU:Eden 区容量和使用量
  • OC、OU:年老代容量和使用量
  • PC、PU:永久代容量和使用量
  • YGC、YGT:年轻代 GC 次数和 GC 耗时
  • FGC、FGCT:Full GC 次数和 Full GC 耗时
  • GCT:GC 总耗时

总结

工作以来除了开发工作,也承担了一部分运维的责任,所以 DevOps 最大的推动力其实就是『不够人』。服务器出现异常,一般来说是由于多种因素共同作用引起的,这就需要我们能够以全局的视角,顺藤摸瓜,各个击破,找到问题最终的根源,才能治标治本。

本文也会不断更新,争取能做一套自动化检测工具,减少排查难度。

参考链接

捧个钱场?

热评文章