内存问题往往是线上环境最容易导致的问题,因为其实对于程序来说,内存总是不够用的。而大多数我们在线上遇到的问题总是一个叫 OOM 的,导致这个问题的原因也有很多,今天我们就来看看,如何在线上定位或者排查这样的问题。

诊断指标

free

命令

free -h

1
2
3
4
[root@Linkin /]# free -h
total used free shared buff/cache available
Mem: 7.6G 326M 6.2G 480K 1.1G 7.0G
Swap: 0B 0B 0B

指标

这个命令可以看到当前设备的内存总体使用情况,以及很清楚的看到交换区的内存使用情况

  • total:总内存
  • used: 已使用内存,包含共享内存
  • free: 未使用内存
  • shared: 共享内存
  • buff/cache 缓存和缓冲区
  • available 新进程可用内存

top

命令

top + M

指标

1
2
3
4
5
6
7
8
9
10
11
12
top - 20:29:20 up 10 min,  1 user,  load average: 0.00, 0.09, 0.10
Tasks: 92 total, 1 running, 91 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.2 us, 0.2 sy, 0.0 ni, 99.5 id, 0.2 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 7.6/8009024 [|||||||| ]
KiB Swap: 0.0/0 [ ]

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1001 influxdb 20 0 566444 81360 16180 S 0.3 1.0 0:11.26 influxd
1025 root 20 0 799412 73760 27816 S 0.0 0.9 0:00.76 dockerd
1023 root 20 0 509024 40648 14164 S 0.0 0.5 0:00.37 containerd
1009 root 20 0 574204 17504 6128 S 0.0 0.2 0:00.32 tuned
746 polkitd 20 0 612344 12260 4772 S 0.0 0.2 0:00.03 polkitd

top 总是能发现绝大多数的问题,按 M 之后会按照内存的使用情况进行排序,你可以清楚的看到内存占用最多的进程是什么。

  • VIRT 进程虚拟内存大小
  • RES 常驻内存,就是实际使用的物理内存,但是不包含 Swap 和共享的
  • SHR 共享内存
  • %MEM 内存使用百分比

vmstat

命令

vmstat -a 5 5

指标

1
2
3
4
5
[root@Linkin ~]# vmstat -a 5 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free inact active si so bi bo in cs us sy id wa st
1 0 0 5970260 487444 1006228 0 0 1 3 4 8 0 0 100 0 0
0 0 0 5970260 487448 1006336 0 0 0 30 588 1132 0 0 100 0 0
  • si: 每秒从交换区写到内存的大小

  • so: 每秒写入交换区的内存大小

  • inact: 非活跃内存大小
  • active: 活跃的内存大小

这个命令可以清楚看到当前是否有交换区的换入和换出,并且能记录下变化过程,还有活跃和非活跃的内存使用情况;同时能看到 CPU 和 IO 的情况,顺便看看是否由其他问题导致。

排查步骤

其实内存问题的排查并没有特别复杂,我们所要做的就是定位:

  1. 确定是否有内存不正常使用的问题
  2. 确定是什么进程或应用占用了过多的内存,能否优化

那下面就说说我一般在线上排查问题常用的步骤,仅供参考

  1. 注意首先肯定是监控!内存问题和别的问题不一样,内存使用过多会直接导致严重的问题,应用可能会直接挂,或者影响别的同时部署的应用,所以做好监控是非常有必要的。(说白了,你不可能一直盯着屏幕看,有时候一个峰值就持续 1 分钟就过去了)
    1. 监控忙时峰值和平均峰值,当应用服务被频繁访问时往往会出现问题
    2. 监控闲时的波峰,如果应用访问不频繁,但是突然又波峰并且很大,需要注意下
    3. 监控持续上涨情况,有的应用部署时间长了才会注意到有内存泄露的问题
  2. 上去就是 free,如果出现问题上去第一步我就会敲这个,确定当前内存使用情况
  3. 然后就是 top 看是不是我自己服务导致的,或者是由于别的组件导致的
  4. 用 vmstat 做个30 秒看看情况,是否还有继续上涨的趋势
  5. 如果应用崩溃了怎么办?有的时候崩溃之后应用本身日志没有任何问题,看起来像是正常退出一样
    1. 看应用本身日志是否显示 OOM
    2. 看 dmesg 是否显示 OOM
    3. 看 var/log/message 是否显示了问题
    4. 看崩溃前后的调用服务日志,看是否由于对应服务业务问题导致
  6. 如果是自身服务导致,根据具体业务分析

排查原因

内存的出现问题的原因有很多,大多数都和具体业务相关,这里也没有办法进行罗列,举几个最常见的案例

突然过大

一般是由于数据量过大,比如查询数据时没有约束最大值导致将数据库全部数据都查询出来;

或者是由于传递参数问题,比如传递了一个 10000000 这样的值,然后用这个长度直接去创建了数组或者别的类型,而实际并没有那么多数据,经常出现于三方调用接口时导致

慢慢变大

如果是使用 java 或者 go 这样带有 gc 的语言会好很多,你不用主动去 free 你使用的内存;但是别妄想着没问题,实际中很多时候用于指针的使用,或者是线程的不断创建,等等原因导致对象无法被 gc 从而也会产生内存泄露。

还有连接池没有设置最大上限,也会有可能慢慢变大。

内存缓存

有时候应用会缓存一些数据到内存中,一般情况下不会缓存很大的数据,可能就是一些热点数据等,大多时候缓存大数据量的时候也会考虑使用 redis,但还是会出现使用内存缓存一些 map 的时候由于用户量突然上来,导致内存占用过多的情况发生。

定时任务

有时候一些应用访问并不多,但是内存占用往往在一段时间之后就会变大且无法释放,有些时候就是由于内部的一些定时任务或者定时器导致内存使用后没还导致的。多见于一些需要设定超时时间,但是超时时间又没有设定默认值的情况。

使用 swap

有时内存突然的上升会导致内核被迫开始使用 swap,那么使用 swap 其实就已经意味着你正在危险的边缘徘徊了,而且使用 swap 往往总伴随一些 IO 问题。附赠一个命令swapoff -a && swapon -a

虚拟内存和缓存

这个不是个问题,但是需要提一句,有的时候有人会看到 VIRT 占用很多,或者缓存和缓冲占用很多,其实这个问题都不大。虚拟内存你占了但是并没有实际分配,缓存和缓冲往往都是内核为了加快访问而做的,当内存不够时会主动进行释放不用担心。

总结

内存问题一般就两种:

  • 一种你是干的(我就要需要那么多内存但其实你没有)
  • 一种你无意识干的(我不用那么多但是实际用了没还)

当出现内存问题时还是要多加注意,针对不同的语言也有不同的处理思路,java 看看虚拟机,go 看看 pprof 等等。