临时抱佛脚,线上问题如何排查? - 图1

来源:低并发编程 地址:https://mp.weixin.qq.com/s/8YBJn8x7Sf9dai9jNkX6Tg

面试时被高频问到,有没有遇到过线上问题,如何解决。

一般我都是大眼瞪小眼,说个看看日志中有没有 NPE 问题,去机器上 top 一下,就没了。

被面试官鄙视了好多次,今天简单梳理一下,目的是为了不大眼瞪小眼即可,等我应付完面试,再给大家好好准备准备这块的思路。

到时候面试被问到了没思路,还能手机上偷偷看看自己写的文章回忆回忆,也不错嘿嘿。

磁盘空间不足问题

先用 df -h 从总体查看磁盘状态

  1. 文件系统 容量 已用 可用 已用% 挂载点
  2. devtmpfs 1.9G 4.0K 1.9G 1% /dev
  3. tmpfs 1.9G 24K 1.9G 1% /dev/shm
  4. tmpfs 1.9G 740K 1.9G 1% /run
  5. tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup
  6. /dev/vda1 50G 12G 36G 25% /
  7. tmpfs 379M 0 379M 0% /run/user/0
  8. tmpfs 379M 0 379M 0% /run/user/1001
  9. /dev/loop4 1003K 17K 935K 2% /mnt

很多都是无用信息,比如 tmpfs 类型的是 linux 的内存型文件系统,里面的数据是放在内存中的,你可 cd 到后面那些目录下玩玩,和操作正常的文件是一样的。

一般就是看挂载点为根目录的 / 的容量咯,这里我只用了 25%,显然还没有达到瓶颈,但如果这里太大了,还要进一步看看是哪个目录大了。

此时用 du -sh * 命令,查看 / 路径下的各个文件和目录的大小。

  1. 0 bin
  2. 166M boot
  3. 134M data
  4. 4.0K dev
  5. 40M etc
  6. 4.0K home
  7. 0 lib
  8. 0 lib64
  9. 16K lost+found
  10. 4.0K media
  11. du: 无法访问"mnt/hello": 输入/输出错误
  12. 13K mnt
  13. 20K opt
  14. du: 无法访问"proc/12896/task/12896/fd/4": 没有那个文件或目录
  15. du: 无法访问"proc/12896/task/12896/fdinfo/4": 没有那个文件或目录
  16. du: 无法访问"proc/12896/fd/4": 没有那个文件或目录
  17. du: 无法访问"proc/12896/fdinfo/4": 没有那个文件或目录
  18. 0 proc
  19. 953M root
  20. 740K run
  21. 0 sbin
  22. 4.0K srv
  23. 2.1G swapfile
  24. 0 sys
  25. 68K tmp
  26. 5.3G usr
  27. 2.9G var

找到最大的那个目录,进去,再次执行这个命令,直到找到最终占地面积特别大的文件或目录为止。

当然,如果你知道里面全都是普通文件,比如你发现你的 log 目录很大,想看看里面哪个具体日志文件过大,也可以用 ls -lh 命令,它的输出会更丰满一些,但我们主要看占地大小,也就无所谓了。

  1. drwxr-x--- 2 www www 4.0K 2 3 14:56 hsperfdata_www
  2. srwxrwxrwx 1 mysql mysql 0 2 3 14:56 mysql.sock
  3. -rw-r--r-- 1 root root 0 6 19 15:33 stargate.lock
  4. drwx------ 3 root root 4.0K 2 3 14:55 systemd-private

另外提一嘴,你如果用 du -h * 去执行的话,整个屏幕会很刺激哟,你可以试试,哈哈~

CPU 与内存使用率过高问题

到了我临场发挥时,唯一能记得住的命令了,那就是 top

  1. top - 20:16:56 up 141 days, 5:21, 1 user, load average: 0.00, 0.01, 0.05
  2. Tasks: 110 total, 1 running, 109 sleeping, 0 stopped, 0 zombie
  3. %Cpu(s): 0.2 us, 0.5 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
  4. KiB Mem : 3880160 total, 135320 free, 852980 used, 2891860 buff/cache
  5. KiB Swap: 2097148 total, 2087676 free, 9472 used. 2737524 avail Mem
  6. PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
  7. 11866 root 20 0 611652 14280 2396 S 0.7 0.4 239:17.79 barad_agent
  8. 16850 root 20 0 1026344 22364 11644 S 0.7 0.6 5:04.40 YDService
  9. 1550 root 20 0 140996 2428 1472 S 0.3 0.1 58:38.76 redis-server
  10. 1575 www 20 0 3610540 358456 14912 S 0.3 9.2 153:55.63 java
  11. 16868 root 20 0 773168 12576 11056 S 0.3 0.3 1:15.83 YDEdr

下面的列表表示不同进程(PID)所占用的资源情况,没错,不仅仅是 CPU 情况。

VIRT 表示使用的虚拟内存数量,RES 表示使用的物理内存数量,SHR 表示使用的共享内存数量,这三者可以从内存角度看该进程的资源占用情况。

S 表示进程的状态,下面的值 S 表示睡眠,D 表示不可中断睡眠,R 表示运行,基本知道这三个就够了。

后面两个值是百分比,%CPU 自然就是 CPU 使用率,%MEM 自然就是内存使用率,看这俩值可以一目了然看谁占用的资源过高了。

TIME 表示累计 cpu 使用时长,感觉没什么用。

COMMAND 表示启动进程使用的命令行,Java 程序的话,可以看看 JVM 启动参数,看是否配置的合理。

如果专门看 Java 进程的情况,可以先 jps 命令找到它的 PID。

  1. 19063 jar
  2. 1575 Bootstrap
  3. 21263 Jps

然后再 top -p 19063 专门看这个 Java 进程的情况。

  1. top - 20:51:07 up 141 days, 5:55, 3 users, load average: 0.09, 0.
  2. Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
  3. %Cpu(s): 0.7 us, 0.3 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.0
  4. KiB Mem : 3880160 total, 139956 free, 952180 used, 2788024 buf
  5. KiB Swap: 2097148 total, 2087420 free, 9728 used. 2638260 ava
  6. PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+
  7. 19063 root 20 0 3533736 101916 13012 S 0.3 2.6 0:04.31

如果再细化到线程,可以加个 -H 参数,top -p 19063 -H

  1. top - 20:52:16 up 141 days, 5:56, 3 users, load average: 0.03, 0.
  2. Threads: 22 total, 0 running, 22 sleeping, 0 stopped, 0 zomb
  3. %Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0
  4. KiB Mem : 3880160 total, 140136 free, 951924 used, 2788100 buf
  5. KiB Swap: 2097148 total, 2087420 free, 9728 used. 2638512 ava
  6. PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+
  7. 19063 root 20 0 3533736 101920 13012 S 0.0 2.6 0:00.00
  8. 19064 root 20 0 3533736 101920 13012 S 0.0 2.6 0:01.16
  9. 19065 root 20 0 3533736 101920 13012 S 0.0 2.6 0:00.01
  10. 19066 root 20 0 3533736 101920 13012 S 0.0 2.6 0:00.01
  11. 19067 root 20 0 3533736 101920 13012 S 0.0 2.6 0:00.02
  12. 19068 root 20 0 3533736 101920 13012 S 0.0 2.6 0:00.00
  13. 19069 root 20 0 3533736 101920 13012 S 0.0 2.6 0:00.00

当然,top 命令已经可以分析内存了,如果想单独分析下内存,可以用小而美的命令,free -h

  1. total used free shared buff/cache available
  2. Mem: 3.7G 934M 155M 500K 2.6G 2.5G
  3. Swap: 2.0G 9.5M 2.0G

这些参数的含义是:

total:内存总数

used:已经使用内存数

free:完全空闲内存

shared:多个进程共享的内存

buffers:用于块设备数据缓冲,记录文件系统 metadata(目录,权限,属性等)

cached:用于文件内容的缓冲

available:真正剩余的可被程序应用的内存数

一共有两行,mem 和 swap,mem 就是内存大小,swap 是交换区,是在物理磁盘上的一块区域,当内存不够用时,可以用这部分区域当内存。

可以用 swapon 命令来看下交换区的使用情况。

  1. NAME TYPE SIZE USED PRIO
  2. /swapfile file 2G 9.5M -2

网络延迟


netstat -a 查看所有连接中的 socket。

  1. Active Internet connections (servers and established)
  2. Proto Recv-Q Send-Q Local Address Foreign Address State
  3. tcp 0 0 0.0.0.0:mysql 0.0.0.0:* LISTEN
  4. tcp 0 0 VM-0-12-centos:6379 0.0.0.0:* LISTEN
  5. tcp 0 0 VM-0-12-centos:memcache 0.0.0.0:* LISTEN
  6. tcp 0 0 0.0.0.0:http 0.0.0.0:* LISTEN
  7. tcp 0 0 0.0.0.0:ftp 0.0.0.0:* LISTEN
  8. tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN
  9. tcp 0 0 VM-0-12-centos:smtp 0.0.0.0:* LISTEN
  10. tcp 0 0 0.0.0.0:irdmi 0.0.0.0:* LISTEN
  11. tcp 0 0 VM-0-12-centos:12419 169.254.0.4:http TIME_WAIT
  12. tcp 0 0 VM-0-12-centos:ssh 114.247.175.201:8829 ESTABLISHED
  13. tcp 0 0 VM-0-12-centos:64390 169.254.0.55:lsi-bobcat ESTABLISHED
  14. tcp 0 0 VM-0-12-centos:ssh 114.247.175.201:14227 ESTABLISHED
  15. tcp 0 0 VM-0-12-centos:ssh 114.247.175.201:22704 ESTABLISHED
  16. tcp 0 36 VM-0-12-centos:ssh 114.247.175.201:15126 ESTABLISHED
  17. tcp6 0 0 [::]:webcache [::]:* LISTEN
  18. tcp6 0 0 [::]:ftp [::]:* LISTEN
  19. tcp6 0 0 VM-0-12-centos:smtp [::]:* LISTEN
  20. tcp6 0 0 [::]:irdmi [::]:* LISTEN
  21. tcp6 0 0 VM-0-12-centos:8006 [::]:* LISTEN
  22. udp 0 0 0.0.0.0:bootpc 0.0.0.0:*
  23. udp 0 0 VM-0-12-centos:ntp 0.0.0.0:*
  24. udp 0 0 VM-0-12-centos:ntp 0.0.0.0:*
  25. udp6 0 0 VM-0-12-centos:ntp [::]:*
  26. udp6 0 0 VM-0-12-centos:ntp [::]:*

netstat -tnpa 命令可以查看所有 tcp 连接的信息,包括进程号。

  1. Active Internet connections (servers and established)
  2. Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
  3. tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 2903/mysqld
  4. tcp 0 0 127.0.0.1:6379 0.0.0.0:* LISTEN 32647/redis-server
  5. tcp 0 0 127.0.0.1:11211 0.0.0.0:* LISTEN 1240/memcached
  6. tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1030/nginx: master
  7. tcp 0 0 0.0.0.0:21 0.0.0.0:* LISTEN 1016/pure-ftpd
  8. tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1201/sshd
  9. tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 1502/master
  10. tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN 1550/./redis-server
  11. tcp 0 0 172.21.0.12:22 114.247.175.201:8829 ESTABLISHED 18961/sshd: root@no
  12. tcp 0 0 172.21.0.12:64390 169.254.0.55:5574 ESTABLISHED 16850/YDService
  13. tcp 0 0 172.21.0.12:22 114.247.175.201:14227 ESTABLISHED 9490/sshd: root@not
  14. tcp 0 0 172.21.0.12:22 114.247.175.201:22704 ESTABLISHED 20259/sshd: root@no
  15. tcp 0 36 172.21.0.12:22 114.247.175.201:15126 ESTABLISHED 9478/sshd: root@pts
  16. tcp6 0 0 :::8080 :::* LISTEN 1575/java
  17. tcp6 0 0 :::21 :::* LISTEN 1016/pure-ftpd
  18. tcp6 0 0 ::1:25 :::* LISTEN 1502/master
  19. tcp6 0 0 :::8000 :::* LISTEN 1550/./redis-server
  20. tcp6 0 0 127.0.0.1:8006 :::* LISTEN 1575/java

得到进程号后就好说了配合 top 命令,ps -ef 命令,查看相关进程信息。

不过这些自带的网络命令,都不太灵活,一般我们有直观查看实时流量,然后进行一波统计分析的需求,这里介绍一个酷酷的命令。

输入 iftop -P 会得到这样的一个实时数据。

临时抱佛脚,线上问题如何排查? - 图2

中间的 <= => 这两个左右箭头,表示的是流量的方向。

TX:发送流量

RX:接收流量

TOTAL:总流量

Cumm:运行 iftop 到目前时间的总流量

peak:流量峰值

rates:分别表示过去 2s 10s 40s 的平均流量

当然这些是从机器上的,如果是一个接口响应耗时过长,一方面可能是由于机器本身所在的网络有问题。

一般这种问题也就是运维同学去解决,或者我们单节点重启一下,换台机器,就搞定了。

还有可能是服务提供方业务耗时严重,这个就需要去排查服务提供方的日志,机器负载,连接池占用情况等,分析问题,这也是我们平时开发碰到的主要问题。

Java 程序的问题分析


这个也算是个八股文吧,平时生产环境中很少会用到,不过我倒是碰到过一些,但都很简单,主要借着故事来方便记忆一下具体命令吧。

有次我们的一个服务,内存是这样的,直到触发了报警。

临时抱佛脚,线上问题如何排查? - 图3

jmap -dump 分析堆内存中的快照,未发现有大对象问题。

临时抱佛脚,线上问题如何排查? - 图4

jmap -heap 查看堆内存设置与当前使用情况,堆内存设置的是 6G。

  1. Attaching to process ID 127, please wait...
  2. Debugger attached successfully.
  3. Server compiler detected.
  4. JVM version is 25.202-b08
  5. using thread-local object allocation.
  6. Parallel GC with 4 thread(s)
  7. Heap Configuration:
  8. MinHeapFreeRatio = 0
  9. MaxHeapFreeRatio = 100
  10. MaxHeapSize = 6442450944 (6144.0MB)
  11. NewSize = 2147483648 (2048.0MB)
  12. MaxNewSize = 2147483648 (2048.0MB)
  13. OldSize = 4294967296 (4096.0MB)
  14. NewRatio = 2
  15. SurvivorRatio = 8
  16. MetaspaceSize = 21807104 (20.796875MB)
  17. CompressedClassSpaceSize = 1073741824 (1024.0MB)
  18. MaxMetaspaceSize = 17592186044415 MB
  19. G1HeapRegionSize = 0 (0.0MB)
  20. Heap Usage:
  21. PS Young Generation
  22. Eden Space:
  23. capacity = 2117074944 (2019.0MB)
  24. used = 1150960080 (1097.6410675048828MB)
  25. free = 966114864 (921.3589324951172MB)
  26. 54.36558036180698% used
  27. From Space:
  28. capacity = 15204352 (14.5MB)
  29. used = 13860864 (13.21875MB)
  30. free = 1343488 (1.28125MB)
  31. 91.16379310344827% used
  32. To Space:
  33. capacity = 15204352 (14.5MB)
  34. used = 0 (0.0MB)
  35. free = 15204352 (14.5MB)
  36. 0.0% used
  37. PS Old Generation
  38. capacity = 4294967296 (4096.0MB)
  39. used = 188289400 (179.56676483154297MB)
  40. free = 4106677896 (3916.433235168457MB)
  41. 4.383954219520092% used
  42. 23604 interned Strings occupying 2341024 bytes.

jstack 查看 jvm 线程运行信息,上传到 fastthread.io 这个网站,直观地看一下,一看线程有点多。

临时抱佛脚,线上问题如何排查? - 图5

一个线程需要占用大约 1M 的空间吧,而且不是占用 jvm 的内存空间,而是会占用操作系统空闲的内存空间。

我们的机器内存是 8G,堆内存占了 6G,线程数这么多快超过 2G 了,再加上操作系统里其他程序占用的内存,内存告警很正常,甚至可能 OOM。

所以,一方面我们可以减少线程数,另一方面可以把堆内存配置得小一点,使得堆内存加上线程占用的操作系统内存,不要超过 8G。

一些无法本地 debug 的调试技巧


有的时候线上忘记在关键节点打日志了,会导致一些问题,比如不知道方法入参的值,不知道某方法中具体某一步的耗时。

我一般用阿里的一款贼牛逼的工具 arthas 来排查。

先申请个线上机器的接近 root 的权限,然后把 arthas 工具下载到机器上,可以直接机器上下载:

wget https://alibaba.github.io/arthas/arthas-boot.jar

使用 watch 命令可以实时观察一个方法的入参和出参。

使用 trace 命令可以跟踪某个方法的耗时,而且可以深入这个方法所调用的方法的各个耗时。

当然我就只用这俩功能,用作忘记打日志时的临时方案了,属于高射炮打蚊子。

实际上人家的功能超级强大,比如 dashboard 全局监控,thread 查看所有线程信息,包括状态和 CPU 使用率,thread -b 甚至可以直接定位到死锁信息,jad 命令进行反编译,ognl 命令查看某一个对象的具体值,大家可以去网上学习下。

今天先介绍这么多,唯一目的就是让我面试再被问到这个问题时,不至于大眼瞪小眼,至于深入嘛,以后再说吧,祝我面试加油!等我忙完这段时间,给大家好好整几篇文章!

本文转自 https://mp.weixin.qq.com/s/8YBJn8x7Sf9dai9jNkX6Tg,如有侵权,请联系删除。