周五下午四点,手机连着弹出十几条告警,云服务器CPU使用率100%、磁盘使用率95%、接口超时率飙升,运维群里炸开了锅,所有人都在问“怎么回事”。这种“云服务器爆满”的场面,干过运维的人多少都经历过。它不一定代表服务器要挂了,但一定代表哪里出了问题——可能是流量突增,可能是代码泄漏,也可能是日志把磁盘塞满了。
这篇文章就是一份应对“爆满”的完整操作手册。我会从根因分析、应急处理、优化预防三个层面,把实际运维中最常遇见的几种“满”逐一拆开讲清楚,包括如何快速定位、如何止血恢复、以及如何在事后避免再次踩坑。适合正在扛生产环境压力的一线运维、后端开发,也适合刚接手云服务器管理、对“爆满”还没有完整应对思路的同学参考。
1. 先分清“爆满”的四种类型
很多人一看到“云服务器爆满”就慌了,其实“爆满”不是一个具体的故障,而是一类现象的总称。CPU打满、内存耗尽、磁盘写满、inode耗尽,这四种“满”的表现、影响、处理方式完全不同,如果分不清类型就乱操作,轻则白忙一场,重则把原本还稳定的系统搞垮。
1.1 CPU打满:系统“转不动”了
CPU打满是最直观的一种爆满。现象是服务器响应极慢,ssh敲个命令都要等好几秒,top命令一看,%Cpu(s)那行接近100%,或者某个进程的CPU占用已经超过核数。
CPU打满的根因通常有这么几类:
- 突发流量导致Web服务/接口服务并发量激增,进程数暴涨,每个请求都要消耗CPU时间片。
- 代码出现死循环或无限递归,某个进程独自吃满一个核甚至多个核。
- 定时任务撞车,比如每天凌晨3点的备份任务和凌晨3点的统计报表任务同时启动,资源竞争瞬间拉满。
- 外部扫描或攻击,大量无效请求打到服务器上,业务进程都在忙着解析非法参数。
这里有个容易被忽略的点:CPU打满不一定是“计算量大”,也可能是“上下文切换太频繁”。如果进程数特别多,CPU大量时间花在切换进程上,实际干活的时间反而不多。判断方法很简单,top命令里看us(用户态)、sy(系统态)、wa(等待I/O)三个值。如果sy特别高,说明内核态占用大,大概率是进程调度或系统调用过于频繁。
类似地,在判断CPU是否真的“满”之前,要先看机器核数。单核机器CPU到100%和8核机器CPU到100%完全不是一回事。单核机器跑个压缩任务就能打满,但这种情况通常不会造成服务不可用;8核机器全满,那才是真的有大问题了。
1.2 内存耗尽:进程被“杀掉”
内存爆满比CPU爆满更危险,因为Linux的内存管理机制会在内存耗尽时启动OOM Killer,直接挑一个占用内存大的进程kill掉。结果就是——你还没找到是谁吃的内存,进程已经被系统“正义执行”了。
内存爆满的典型表现是:top或free命令显示available几乎为0,swap被大量使用,dmesg日志里有Out of memory相关记录,业务进程莫名其妙消失或重启。
常见根因包括:
- 代码内存泄漏,尤其是长连接型服务,每个连接泄漏一点,时间久了内存就被慢慢吃光。
- 缓存设置不合理,比如Redis或本地缓存没有设置上限,数据无限往内存里堆。
- 并发量过大,每个请求都要创建大量对象或大数组,瞬时内存飙升。
- JVM/PHP等运行时堆内存配置过大,超出了服务器物理内存范围。
这里需要纠正很多人的一个误区:看内存不能只看free命令的free列,Linux会把空闲内存拿来做缓存,free显示很低不代表内存不够。要看的是available字段,这才是“真正还能用多少”的数值。举个例子,一台4G内存的服务器,free显示只有200M,但available显示2.8G,那其实是健康的;反之如果available不到几百M,那才需要担心。
1.3 磁盘写满:一切“静默”失败
磁盘满是一种非常隐蔽的故障,因为它不会立刻让服务器宕机,而是让很多操作“悄悄失败”。比如日志写不进去、数据库事务提交失败、临时文件创建不了,但主进程还在运行——表面看一切正常,实际上业务已经半瘫痪了。
磁盘打满的常见根因:
- 应用日志和系统日志无限增长,有的业务一天能产生几十GB日志。
- 数据库的binlog、redo log没有定期清理,尤其主从复制停了之后binlog会一直堆积。
- 备份文件、导出文件、临时文件长期积压,没人清理。
- 容器镜像、layer、孤儿卷占空间,如果用的是Docker,/var/lib/docker打满的情况非常常见。
判断磁盘是否满,用df -h就能看到使用率,但要注意df的统计口径有两个维度——一个是块使用率,一个是inode使用率。块使用率100%代表数据写不进去了,inode使用率100%代表连创建新文件都不行了。很多人只盯df -h,忽略了df -i,结果磁盘看着还有几百G,但就是创建不了文件,最后排查半天发现是inode耗尽了。
1.4 inode耗尽:小文件的“隐形杀手”
inode耗尽是最容易被忽视的一种爆满。inode是Linux文件系统用来记录文件元数据的结构,每个文件或目录都要占用一个inode。磁盘空间还剩很多,但如果文件数量巨大,单个文件很小,inode就可能先一步耗尽,结果是:mkdir创建目录、touch创建文件都会失败,提示No space left on device,但df -h明明显示还有大量空间。
这种问题最常见于大批量缓存文件场景——比如生成缩略图、存储聊天记录、堆积一次性验证码临时文件等。我自己就遇到过一次:一个图片处理服务把处理结果先写到磁盘上的临时目录,写完没及时删除,几个月下来积累了数十万个几KB的小文件,直接把inode干到99%。
排查方法也很简单,df -i查看inode使用率,然后用for循环找出文件数最多的目录,一层层定位到具体是哪个目录、哪类文件导致的。这种问题一旦发生,应急清理只能治标,真正的解决方案是在应用层面引入定时清理机制,或者改用对象存储这类不依赖本地inode的存储方案。
2. 根因分析:从现象到定位的完整思路
分清类型只是第一步,真正难的是在最短时间内锁定“罪魁祸首”。生产环境中每一分钟都在产生成本,如果排查思路混乱,很容易陷入“看哪个都像、哪个都不确定”的困境。
2.1 排查顺序:先看趋势,再看进程,最后看日志
面对“爆满”告警,我建议的排查顺序是固定的:先看监控趋势图,再看实时进程状态,最后看日志。这个顺序不可颠倒,因为趋势图能快速告诉你“是突然涨上来的”还是“慢慢堆积的”,这个信息直接决定排查方向。
如果CPU在5分钟内从30%直线拉到100%,那基本是突增流量、死循环、被攻击这几类快变量导致;如果CPU是两天内从50%慢慢爬到99%,那大概率是内存泄漏、日志增长这类慢变量导致,只不过最后集中爆发了。
看趋势图的同时,用top和ps命令看当前进程。top后按CPU排序、再按内存排序,各看一遍,基本能锁定可疑进程。比如发现某个Java进程CPU占800%,那就马上用jstack、jmap这类工具去进一步排查线程级别的瓶颈。
最后才是看日志。很多运维同学一上来就翻日志,这是错的做法——日志量太大,没有明确目标时翻日志就像大海捞针,效率极低。正确的姿势是先通过趋势和进程缩小范围,带着“怀疑某个服务某个时间段出了问题”这样一个具体目标去翻对应的日志,这样才能快速命中关键错误。
2.2 快速判断:是容量不足还是代码问题
区分“容量不足”和“代码问题”很重要,因为两者的处理方向完全相反。容量不足要扩容、要限流,代码问题要修复、要回滚。
怎么快速区分?看资源消耗的“异常度”。比如正常业务高峰CPU也就用到60%,这次突然到100%,但请求量并没有明显变化——那基本是代码问题。反过来,如果请求量相比平时翻了几倍,CPU和内存飙高只是“正常反应”,那就是容量问题。
这里有个实用的判断公式:看单位请求的资源消耗。拿Web服务举例,正常情况下每个请求消耗多少CPU、多少内存是有大致基准的。如果请求量没大变化,但单位请求的CPU消耗是平时的好几倍,那几乎可以断定是某次发布引入了缺陷——比如正则表达式写成了灾难性回溯、SQL没走索引导致全表扫描、出现了N+1查询等。带着这个方向去看最近一次发布变更,往往很快就能定位。
2.3 最容易被忽略的三个“隐形爆满点”
有些爆满情况特别容易让人走弯路,我列出三个高频“坑”,都来自真实踩坑经历。
第一个是“负载高但CPU不高”。用top查看时load average高达十几,但CPU id%很高,这种情况说明CPU其实在等待,大量进程阻塞在I/O或锁上。常见于磁盘I/O瓶颈——比如有人把数据库和备份任务放同一块云盘上,备份一跑,整个数据库I/O就堵塞了;也可能是代码层面死锁,线程互相等待,谁也不干活。
第二个是“内存充足但系统卡顿”。这种通常是swap导致的。Linux在内存压力大时会大量启用swap,而云服务器的swap往往在系统盘上走的是磁盘I/O,比内存慢好几个数量级。结果就是内存看起来释放了,但读写频繁在不同内存页之间颠簸,系统越来越卡。排查重点不是看内存剩余量,而是看swpd和si/so指标,si/so持续不为0就说明在频繁换页,说明内存压力其实已经很大了。
第三个是“带宽打满导致服务假死”。云服务器入口带宽或出口带宽被占满时,外部请求进不来,响应也出不去,表现和服务器宕机非常相似——连接超时、ping不通、业务完全不可用。但在服务器上执行命令还正常,CPU、内存、磁盘都健康。这种情况下用iftop或云平台自带的带宽监控就能一眼看出来,但很多人第一时间根本想不到去看带宽,先重启了服务器再说,结果重启完该堵还是堵。
3. 应急处理:快速止血的正确姿势
找到根因之前,先要想办法让业务“活下来”。应急处理有一条铁律:止血优先于根因修复。哪怕你脑子里已经能猜出是哪行代码出了问题,也要先把损失控制住,再从容去改代码。
3.1 止血三板斧:扩容、清理、限流
不同的“满”对应不同的止血手段,我用一个表格把最常用的三板斧对应关系列清楚:
| 爆满类型 | 首选止血手段 | 说明 | 注意点 |
|---|---|---|---|
| CPU打满(突增流量) | 扩容实例/增加节点 | 水平扩容最快,把流量分摊开来 | 要确认服务是否无状态,有状态需要先梳理会话保持策略 |
| CPU打满(代码缺陷) | 回滚最近发布版本 | 快速恢复到最后一次稳定版本 | 回滚前保留现场,用jstack等技术手段抓现场证据 |
| 内存耗尽 | 重启高占用进程/扩容内存规格 | 重启能快速释放被占内存 | 重启前先记录进程的启动参数,避免起不来 |
| 磁盘写满 | 清理垃圾文件 | 日志、旧备份、临时文件都可以动 | 用lsof查看已被删除但仍被占用的文件 |
| inode耗尽 | 批量删除小文件/迁移存储方案 | 应急删除加长期方案调整 | 删除前先确认目录归属,别把业务数据当垃圾清掉 |
| 带宽打满 | 启用限流/CDN拦截/封禁来源IP | 从入口处削减流量 | 小心误伤正常用户,必要时用灰度策略 |
扩容这条要展开说一下:云服务器的扩容不像你想象中那么“随手一点就好”。实例规格变更需要停机或热迁移,如果业务对高可用有要求,更稳妥的做法是新增一台配置相同的机器,负载均衡后面把流量分一部分过去,确认没问题再逐步切换。如果业务是有状态的——比如自建数据库、本地磁盘存session——单纯加机器可能导致数据不一致,这种情况先把耗尽源头的服务降级或限流,再考虑扩容才是正确顺序。
3.2 清理磁盘时千万注意:别删到“活文件”
清理磁盘是应急处理里最常见的操作,但这里有一个极其容易踩的坑:你以为删了文件空间就释放了,实际却没有。
原因在于Linux的删除机制。当进程打开一个文件时,文件占用的inode不会被真正释放,直到那个进程把文件关闭为止。所以如果你用rm删了一个正在被服务写入的日志文件,文件名字消失了,但空间并没有释放——进程还在往这个“无主文件”写数据,df查看空间还是满的,而且因为文件没了,之后排查都找不到这个罪魁祸首。
正确做法是:先用lsof | grep deleted找出已被删除但仍被占用的文件及对应进程,然后要么重启进程释放句柄,要么用cat /dev/null > 文件路径把现有日志内容清空但不释放句柄,比如cat /dev/null > /var/log/app/error.log,这样既清出了空间,进程写入也不会中断。后者在应急场景下更常用。
另外提醒一句:清理操作顺序也有讲究。先清临时目录、缓存目录(/tmp、/var/cache),再清旧日志(按时间保留最近7天或30天),最后才考虑动备份文件。数据库相关文件的清理尤其要慎重,比如binlog,要确认从库已经追上主库再清理,否则主从复制断掉,后面更麻烦。
3.3 保留现场:诊断完了再恢复
应急处理过程中最容易犯的错误是“手滑”——还没搞清楚原因,就先把服务重启了。重启确实能在短时间内让CPU和内存恢复平静,但它同时也把现场毁掉了。进程重启后,内存状态没了,线程堆栈没了,进程打开的文件列表没了,你失去的不只是“证据”,更是定位根因的唯一途径。
正确的做法是:重启之前,先做一轮“现场快照”。用什么工具?看情况:
- 如果是CPU打满,用
top -H -p PID抓线程号,再用jstack或gdb把线程栈打出来,或者直接jstack PID > /tmp/jstack.log保存; - 如果是内存打满,用
jmap -dump:live,format=b,file=/tmp/heap.hprof PID导一份堆快照,之后用MAT或VisualVM做离线分析; - 如果是磁盘满,用
du -sh /*逐层定位大目录,同时保存一份df和lsof的输出; - 如果是网络问题,用
ss -anp和iftop保存当前的连接状态和流量分布。
做完全部操作、保存完现场,再重启、再扩容、再清理,心里就有底了。哪怕后边真需要找根因,手里有资料,分析起来就不至于全靠猜。
4. 优化与预防:把“爆满”扼杀在萌芽期
止血解决的是今天的问题,预防解决的是明天的问题。云服务器爆满这类故障,只做应急不做什么长效优化,就等于在同一个坑里反复摔。我后面说的这些事情,也是我在处理完多次“爆满”事故后总结出来的经验,属于“踩坑后才知道重要”的那一类。
4.1 日志治理:设定上限和轮转策略
日志是磁盘满满的最常见源头,也是最容易治理的一项。Linux自带的logrotate就可以解决绝大部分日志增长问题,很多应用框架(如log4j2、Python logging)也支持内置的rotation配置。关键是你要设置,而且要好别只设“按大小切割”不设“保留数量”。
我推荐一套可以直接抄作业的日志策略:
- 应用日志按天切割,保留最近7天(业务审计类按合规要求另说);
- 单文件大小上限200MB,超过就切分;
- 总日志目录空间上限预留整块磁盘的30%;如果业务日志特别大,就把日志单独挂载一块云盘再设好分区的限制;
- 定期跑一条命令检查:
find /var/log -type f -size +500M,手动揪出异常增长的大日志。
同时,日志级别的设置也要控制。生产环境一般开INFO就够用,DEBUG只在排查问题时短期开启,用完即关。有的团队“习惯性”开着DEBUG上线,日志量是INFO的好几倍,磁盘和CPU双重开销,真是浪费资源。
4.2 监控告警体系:配置比想象中重要得多
没有监控的服务器就像没装仪表盘的汽车,出事了才靠感觉判断。但“有监控”和“监控有效”完全是两回事。
我见过太多团队:监控面板倒是装了,CPU使用率、内存使用率、磁盘使用率都有一堆图,但告警阈值设置得毫无章法。要么阈值定太高(比如磁盘90%才告警),等收到告警时已经来不及优雅处理;要么阈值定太低(CPU超过50%就发短信),运维被告警淹到麻木,等到真的出现问题时反而没人看了。
给出一个我实测下来比较合理的告警阈值参考:
| 指标 | 预警值 | 告警值 | 说明 |
|---|---|---|---|
| CPU使用率 | 70% 持续10分钟 | 90% 持续5分钟 | 预留弹性空间,避免误报 |
| 内存available | 低于20% | 低于10% | 低于10%时OOM风险很高 |
| 磁盘使用率 | 80% | 90% | 80%开始提示清理,90%必须处理 |
| inode使用率 | 70% | 85% | 修复小文件问题的启动点 |
| 负载 | 核数×0.7 | 核数×1.2 | 按核数算才有意义 |
| 带宽 | 80% 持续5分钟 | 90% 持续10分钟 | 持续观察入口和出口流量 |
这里有一个“持续时长”的概念值得单独说一下。单一瞬间的CPU高不需要告警,可能是正常抖动;要设置成“持续X分钟超过阈值”才触发告警,这样既能躲开瞬时尖峰,也能抓出真正的持续性问题。
告警渠道建议:短信/电话留给高优先级,IM工具通知给普通预警。别让所有告警都往一个渠道涌,否则重要告警被淹没,那就失去告警的意义了。
4.3 容量规划:别等满了再买机器
容量规划这个事,在很多中小团队里完全是空白。没有预算压力的时候大家习惯“先开着,满了再加”,但云服务器的扩容是有限度的——实例规格有上限,单块云盘有上限,而扩容过程本身也有成本和风险。等满了再买,业务要被影响一阵子。
我的建议是给自己定一个“容量安全水位”概念:每一台服务器、每一块云盘,都给自己留30%的空闲余量作为安全缓冲。CPU长期超过70%、内存available长期不足20%、磁盘长期超过75%,这些都属于“需要关注并准备扩容”的信号,但如果放任不管,最终一定会变成“爆满”。
具体的容量规划动作可以这么落地:
- 每季度体检一次:查看这季度CPU、内存、磁盘的峰值和趋势,预测未来3个月的增长曲线。
- 提前做预算:预测到2个月后磁盘会到80%,现在就申请扩大云盘容量,不要等到变成事故再处理。
- 压测先行:新服务上线前,拿压测工具(某压测工具、某压测平台均可)模拟峰值流量打一遍,看单实例能抗多少QPS,做到心里有数。很多“平时没问题、一上量就崩”的事故,根子上就是没压测过的锅。
4.4 分布式架构下的“爆满”治理
现在不少业务已经不是单机了,后端某个服务挂着好几台实例,但“爆满”依然会以另外的方式出现——不是某台机器满了,而是整个集群的某个维度倾斜了。
最典型的场景是负载不均。某个接口实例的流量比其他实例高一截,一台机器CPU跑到90%,其他机器只有30%。这种情况通常不是机器本身的问题,而是负载均衡策略或会话保持策略的问题。排查的时候要把视角从单机抬到整个集群,看流量分布曲线,别盯着一台机器死磕。
另一个典型场景是连接数打满。数据库连接池、Redis连接数、或是后端的并发连接一旦超过阈值,新请求就会被拒绝,表现又类似于“服务器爆满”。处理方案是压测时就要测出连接数上限,然后设置连接池上限和排队策略,再加上连接数的监控告警。
分布式系统的“爆满”治理逻辑更强调全局思维——容量的概念从单台机器变成了整个服务集群,单点优化解决不了问题,要做的是按服务维度做容量评估和流控预案。
5. 常见问题与排查技巧实录
这部分我把多年来遇到的高频问题和对应的排查技巧集中写一下,相当于一份速查表,关键时刻翻一下能省很多时间。
5.1 “服务器重启后负载正常了”——然后呢?
这是最常见的“假治愈”场景。内存泄漏的进程一重启,内存释放,负载恢复正常;但代码里的泄漏如果没修复,过几天又会满,只是时间早晚的问题。把“重启后正常”误判为“故障已解决”,是运维里最大的误区。
正确的姿态是:重启只是应急恢复手段,后续必须做根因分析。是代码引入了内存泄漏?是某个定时任务每天累积生成临时文件?是连接池参数设置不合理?把根因挖出来、修复掉、再验证,才算真正处理完。为了帮你把这种场景做完整,建议在重启后专门安排一个观察期,连续盯资源曲线72小时,确保曲线平稳才能放心关闭工单。
5.2 排查命令组合包
以下命令组合基本覆盖了“爆满”排查的绝大多数场景,建议收藏:
# 查看系统整体负载和CPU占比 top # 按CPU排序显示进程 top -o %CPU # 查看内存使用详情(关注available) free -hm # 查看磁盘空间和inode df -h df -i # 查找大文件 find / -type f -size +100M -exec ls -lh {} \; 2>/dev/null | sort -k5 -rh | head -20 # 查找已删除但仍被占用的文件 lsof | grep deleted # 查看磁盘I/O占用 iostat -x 1 # 查看容器资源使用 docker stats --no-stream # 查看某个进程的详细栈(Linux原生环境) cat /proc/PID/stack每个命令都有自己的“使用场景偏好”,不是让你把所有命令一次性跑一遍,而是根据现象来选择组合。比如CPU高的场景,核心就是top + ps + jstack(如果Java)的组合;磁盘满的场景,核心就是df + du + lsof的组合。把命令组合想清楚,会比漫无目的地乱敲高效得多。
5.3 避坑清单:写在最后
最后按实际经验列几条高频踩坑,供你作为“雷区”标记:
第一,不要在磁盘满的时候硬启动数据库。磁盘满时数据库的状态文件本身就可能不完整,这时候重启很可能启动不起来,处理难度会比原来更大。应该先清理出空间,再尝试启动。
第二,不要在未确认文件归属时删除文件。生产环境里删除操作前必须确认,尤其不要乱删根目录下的“看起来没用”的目录,有些是系统运行所需的临时目录,删了之后反而引发新故障。
第三,不要把所有告警都设置成电话通知。告警疲劳是真实存在的,对重要告警的感知会被大量低优告警淹没。分级通知体系一定要建立起来。
第四,不要把“监控指标正常”当成“系统健康”。很多问题不会立刻体现在资源指标上,比如慢SQL、响应时间变长、错误率升高,一样要纳入观测体系。资源指标只是基础,业务指标才是最终的健康晴雨表。
我个人还是那句话:云服务器“爆满”其实不可怕,可怕的是没有方法地乱处理——不清楚现象就重启,不知道根因就扩容,不做复盘就收工。按前面说的思路走一遍,先明确类型,再定位根源,然后止血,最后做预防,大部分由“爆满”引发的生产事故,其实都是可以避免的。下次再看到告警弹出来,别急着慌,先打开命令行,一步步来就好。