李涛自留地
回到顶部
首页 > 建站 > 服务器内存不足OOM killed进程?排查服务器内存不足优化实战

服务器内存不足OOM killed进程?排查服务器内存不足优化实战

文章目录

昨晚12点收到服务器告警,PHP-FPM进程被OOM Killer干掉了,网站直接502。排查发现是Redis缓存没设置上限,疯狂涨内存。这次把完整的排查和优化流程写下来。

\n

文章配图

\n

什么是OOM Killer?

OOM是"Out Of Memory"的缩写。当Linux服务器内存用完时,内核会启动OOM Killer机制,选择一些进程杀掉释放内存。被杀掉的进程通常是没有"保护"的应用程序(比如PHP-FPM、MySQL、Java等),而系统进程(如init、sshd)一般不会被杀。

现象: 服务器突然变卡,某个进程突然消失,查看系统日志能看到"Out of memory: Kill process"字样。

排查第一步:查看内存状态

# 查看整体内存使用
free -h

# 输出示例: # total used free shared buff/cache available # Mem: 3.9G 3.6G 120M 88M 200M 250M # Swap: 2.0G 1.8G 200M

关注几个指标:
- used:已使用的内存
- buff/cache:系统缓存(可释放)
- available:实际可用的内存(这才是关键)
- Swap:如果used接近total,看Swap用了多少

注意: "available"才是真正的可用内存。buff/cache占的内存虽然算used,但系统需要时可以快速释放。

排查第二步:找出谁吃掉了内存

# 按内存使用排序,查看前10个进程
ps aux --sort=-%mem | head -15

# 输出示例: # USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND # www 1234 2.5 15.3 2100000 600000 ? Sl 昨 12:34 php-fpm: pool www # mysql 5678 1.2 8.7 1800000 340000 ? Sl 昨 08:22 mysqld # redis 9012 0.8 5.2 900000 200000 ? Sl 昨 14:10 redis-server

重点关注 RSS 列(实际占用物理内存),看看哪个进程占得最多。

### 常见内存杀手

1. PHP-FPM:子进程过多或某个PHP脚本内存泄漏
2. MySQL:查询缓存过大或连接数过多
3. Redis:缓存数据过大且没有设置maxmemory
4. Java应用:JVM heap设置过大
5. Nginx:一般不吃内存,但keepalive连接过多会累积

排查第三步:分析具体进程

### PHP-FPM内存分析

# 查看PHP-FPM各进程内存占用
ps aux | grep php-fpm | awk '{printf "%s %.1fMB %s\n", $11, $6/1024, $1}'

# 查看PHP-FPM配置 php -i | grep -E "memory_limit|max_execution_time"

如果某个PHP脚本占用内存异常高(比如超过200M),很可能是有内存泄漏或大数组操作。

排查方法:
- 查看error_log中是否有OOM相关错误
- 用xdebug或blackfire分析脚本内存消耗

### MySQL内存分析

# 查看MySQL当前连接数和内存使用
mysql -e "SHOW PROCESSLIST;"
mysql -e "SHOW STATUS LIKE 'Threads_connected';"

# 查看MySQL配置推荐的内存分配 mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"

# innodb_buffer_pool_size 应该占物理内存的50-70%

如果innodb_buffer_pool_size设得太大,MySQL会吃掉大部分内存。

### Redis内存分析

# 查看Redis内存使用
redis-cli info memory

# 关键字段: # used_memory_human: 已使用内存(人类可读) # used_memory_peak_human: 峰值内存 # maxmemory_human: 最大内存限制(0表示无限制) # mem_fragmentation_ratio: 内存碎片率

如果maxmemory_human显示0,说明Redis没有设置上限,这是最常见的OOM原因。

修复:

# 设置Redis最大内存限制
redis-cli config set maxmemory 512mb
redis-cli config set maxmemory-policy allkeys-lru

allkeys-lru 策略表示当内存满时,淘汰最近最少使用的键。这是Redis生产环境的推荐配置。

优化方案

### 方案1:增加Swap分区

如果物理内存不够,加Swap是最经济的方式:

# 创建2G swap文件
dd if=/dev/zero of=/swapfile bs=1M count=2048
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

# 写入fstab确保重启生效 echo '/swapfile none swap sw 0 0' >> /etc/fstab

# 调整swappiness(越小越倾向使用物理内存) sysctl vm.swappiness=10

建议: swappiness设为10而不是0(0在部分内核有bug),这样系统会优先用物理内存,满了再切swap。

### 方案2:调整PHP-FPM进程数

# 进入PHP-FPM配置文件
nano /etc/php/7.4/fpm/pool.d/www.conf

# 调整以下参数: pm.max_children = 20 # 最大子进程数 pm.start_servers = 5 # 启动时子进程数 pm.min_spare_servers = 5 # 最小空闲进程数 pm.max_spare_servers = 10 # 最大空闲进程数

# 重启PHP-FPM /etc/init.d/php-fpm-74 restart

计算公式: max_children = (总内存 - 系统使用 - MySQL使用) / 每个PHP进程内存

一般每个PHP-FPM进程占用30-80M内存。2G服务器建议设10-15,4G服务器设20-30。

### 方案3:调整MySQL innodb_buffer_pool_size

# 查看当前配置
mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"

# 如果是默认值(128M),对于有2G+内存的服务器来说太小 # 对于4G服务器,建议设置为2G(物理内存的50%) # 修改my.cnf nano /etc/my.cnf

# 添加或修改: [mysqld] innodb_buffer_pool_size = 2G

# 重启MySQL /etc/init.d/mysqld restart

### 方案4:清理Redis缓存

# 查看Redis大key(帮助定位谁占了最多内存)
redis-cli --bigkeys

# 删除特定key(谨慎操作) redis-cli del "cache:article:page:*"

# 或者清理所有过期key(不会阻塞) redis-cli expireall 0

监控和告警

设置监控防止OOM再发生:

#!/bin/bash
# monitor_memory.sh - 内存监控脚本

THRESHOLD=80

# 获取内存使用率(百分比) used_mem=$(free | awk '/Mem:/ {printf "%.0f", $3/$2 * 100}')

if [ "$used_mem" -gt "$THRESHOLD" ]; then echo "$(date): 内存使用率 ${used_mem}% 超过阈值 ${THRESHOLD}%" >> /var/log/memory_monitor.log # 这里可以加邮件/钉钉告警 # 自动清理缓存 sync; echo 3 > /proc/sys/vm/drop_caches echo "$(date): 已清理缓存" >> /var/log/memory_monitor.log fi

加到crontab:

crontab -e
# 每5分钟检查一次
*/5 * * * * /path/to/monitor_memory.sh

总结

排查OOM问题的核心思路:

1. free -h 看整体:确认内存确实不够用
2. ps aux 找元凶:哪个进程吃内存最多
3. 针对性优化:调小进程数、设上限、加swap
4. 持续监控:设置告警防止再次发生

最常见的OOM原因:Redis没设maxmemory + PHP-FPM子进程过多 + 物理内存太小。三个原因叠加,服务器不爆才怪。


若非作者标注皆为李涛自留地原创文章,转载或复制请以超链接形式并注明出处。