资讯动态

服务器负载过高怎么办?5招从快速止损到彻底治理

发布时间:2026/10/14 12:02:45 来源:尧图企业网站定制
服务器负载过高这个问题我几乎每几个月就会碰上一次。有的是半夜被报警电话叫醒有的是客户那边页面已经打不开了才找到我还有一次最离谱光是登录服务器执行命令都要等半分钟。说实话服务器负载高本身不可怕可怕的是不知道它为什么高然后一通乱操作把业务搞得更糟。这篇文章我把自己这几年排查和处理的思路整理出来配合5个能直接上手的实用方法从快速止损到彻底治本一篇讲完。先说清楚适用范围。无论你是云服务器、物理机还是虚拟主机无论跑的是Nginx、MySQL、Java应用还是Node服务负载过高的底层逻辑都差不多。文章适合运维新手、全栈开发以及被服务器卡顿问题折磨到想砸电脑的朋友。读完至少能让你的服务器先喘过气再慢慢谈优化。1. 动手之前先搞清楚“负载”和“卡顿、宕机”到底是什么关系很多朋友一看到load average超过CPU核数就紧张直接重启服务器。这种做法偶尔能救急但它治标不治本而且是有代价的。重启意味着所有连接断开缓存全部丢失如果是数据库主节点甚至会触发主从切换后果可能比负载高本身还严重。所以动手之前先花两三分钟搞清楚状态。1.1 负载值不是CPU使用率别搞混了load average表示的是“可运行进程”加“不可中断睡眠进程”的平均数量。简单理解它就是“有多少进程在排队等待CPU”。这个数值是整数加小数比如load average: 8.17, 6.19, 5.09三个数字分别代表过去1分钟、5分钟、15分钟的平均值。判断是否超标不能只看数字大小要看CPU核数。nproc查一下逻辑核数量如果机器是4核负载长期超过4就说明真的有过载如果是8核负载到6反而是安全的。我见过一个常见的误解是“负载不能超过1”这在单核机器上成立但在多核服务器上完全是错的。还要注意一点负载高不等于CPU忙。进程可能卡在磁盘I/O上等待数据这种状态叫不可中断睡眠同样会拉高负载但CPU其实很闲。所以排查时先看CPU占用率再看负载值两者都高是CPU瓶颈CPU不高但负载高优先怀疑磁盘。1.2 三种典型的“伪高负载”场景第一种是磁盘I/O瓶颈。数据库实例跑着大量慢查询或者日志在疯狂刷盘磁盘读写能力跟不上大量进程阻塞在I/O等待上负载飙升但CPU占用率却很低。这种场景下重启大概率没用因为重启之后业务流量进来问题立刻复发。第二种是内存不足导致Swap频繁交换。当物理内存耗尽系统开始使用Swap分区而Swap的读写速度远低于内存会导致进程运行极慢。这时候负载可能看起来不高不低但系统整体卡顿感非常明显就像你电脑同时开了几十个网页的体验。第三种是进程数暴涨。连接数突然增加每个连接都会消耗一定的内核资源。比如PHP-FPM或Java线程池配置过小请求积压快速创建新进程或线程大量进程在排队负载瞬间被打满。这种场景往往伴有CPU占用率持续在90%以上。明确问题域之后再进入具体处理环节。2. 第一步三分钟快速定位负载源别靠猜处理服务器高负载最忌讳的就是“病急乱投医”。我在实际操作中总结了一套快速定位思路正常情况下三分钟内就能锁定大方向。核心原则是“先看数据再下结论”。2.1 top命令的正确打开方式登录服务器后第一个命令永远是top这个不用多说但很多人忽略了它的细节。top -c-c参数会显示完整的命令行能看出是哪个具体的程序在跑。进入top界面后按大写P按CPU占用率排序按大写M按内存占用排序。重点看两列%CPU和%MEM。但光看这两个还不够。建议用top -u 用户名按指定用户过滤很多业务进程运行在专属用户下比如www或者mysql。另外top默认刷新的间隔是3秒排查时按数字1展开显示每个CPU核心的占用情况有时你会看到个别核心跑到100%但整体才30%这种情况和单线程应用卡在某段逻辑上有关。2.2 用vmstat、iostat看清I/O侧的问题CPU负载偏高直接用top就能锁进程。但如果CPU不高负载却上去了问题大概率出在磁盘I/O上。这时候vmstat 1 5或iostat -x 1 5就派上用场了。vmstat 1 5重点看waI/O等待时间和st被虚拟机偷走的时间。如果wa持续在30%以上说明磁盘快忙不过来了。iostat -x 1 5则能进一步定位到具体是哪块云盘或SSD设备看%util是不是已经接近100%。此时再用lsof或pidstat配合定位到具体进程。我在一次排查中遇到的情况是MySQL的慢查询日志和二进制日志同时写在同一块云盘上大量同步写导致磁盘%util长期打满数据库所有操作都被拖慢。解决办法很简单把日志拆分到另一块独立磁盘问题当场缓解。2.3 快速形成“负载诊断速查表”一边看数据一边对号入座。我列一个常用速查表找问题时直接按行对照现象组合可能的根因优先处理动作CPU占用率高 负载高程序死循环、单机并发过大、恶意扫描找出CPU占用最高的进程分析后再决定杀或优化CPU中等 负载高磁盘I/O瓶颈、Swap交换检查vmstat的wa列定位磁盘占用进程CPU低 负载高不可中断睡眠过多通常是磁盘/网络等待检查D状态进程定位阻塞点内存占用高 卡顿内存不足Swap频繁看Swap使用量考虑调参或扩容进程数暴涨 负载飙升连接数突增或线程池配置过小调整应用并发参数必要时临时限流这套速查表帮我解决了不少“看起来毫无头绪”的问题。定位问题源之后再进入5个实用方法的具体操作。3. 方法一快速止损一分钟内先让服务器“喘上气”所谓的“快速止血”就是在还没想清楚根因前先把对系统伤害最大的因素控制住。这一步的目标不是解决问题是避免问题扩大。很多卡顿和宕机都是因为负载持续过高触发系统保护机制或者OOM Killer开始随机杀进程才导致服务不可用。3.1 找到“搞事”的进程再动手快速止损的理想状态是知道哪个进程是元凶把它处理掉。比如某个Java进程CPU占用200%且持续不降重启这个应用远比重启整台服务器影响面小。ps -eo pid,ppid,user,%cpu,%mem,cmd --sort-%cpu | head -20这条命令直接列出CPU占用最高的20个进程。看到结果后判断这个进程是否是核心业务。如果是僵尸进程、日志清理任务卡死、备份命令异常、被入侵后的挖矿程序直接kill -9没问题。但如果是核心业务进程先别急接着往下看。3.2 最稳妥但容易忽略的手段服务优雅重启核心业务进程状态异常但还没完全死掉时不能粗暴kill。正确做法是通过管理工具重启比如systemctl restart xxx或supervisorctl restart xxx。注意先把服务从负载均衡或SLB上摘下来等五分钟再重启这样能避免重启窗口期的流量全部打到别的节点上把另几台机器也压垮。还有一点服务器负载高时你执行命令本身就慢systemctl restart可能卡在等待。这种情况下给进程发SIGTERM信号等待它优雅退出如果10秒还没退出再升级到SIGKILL。这相当于先礼后兵。kill -15 pid sleep 8 kill -9 pid3.3 重启服务器这件事尽量放最后做说实话重启是最尴尬的“解决方案”。它不解决任何根因只是把系统状态清零业务流量回来之后问题大概率重演。而且重启过程中数据库可能需要做崩溃恢复缓存服务需要重新预热期间会产生大量冷请求反而造成二次冲击。我的建议是只有系统已经无法登录、命令执行没有任何响应或者内核出现严重异常时才考虑强制重启。重启前至少拍一张top和vmstat的截图能保留多少现场就保留多少为事后排查留证据。4. 方法二限制资源占用给系统设置安全边界如果服务器经常因为负载过高而卡顿说明资源使用没有边界某些场景下需要主动限制资源占用。这个方法不是治本但能在突发流量或异常进程出现时保护整体服务的可用性。4.1 用systemd设置CPU和内存上限现代Linux发行版普遍使用systemd管理服务给服务配置CPUQuota和MemoryMax非常方便。[Service] CPUQuota200% MemoryMax2G MemoryHigh1.5GCPUQuota200%表示该服务最多使用2个完整CPU核心。MemoryMax2G是硬限制超过就会触发OOM Kill。MemoryHigh1.5G是软限制超过后会增加回收内存的压力但不立即杀进程。配置完成后执行systemctl daemon-reload和systemctl restart service。好处是异常代码导致的CPU飙高会被限定在配额内不会拖垮整个系统。4.2 用cgroup限制特定进程组有些老服务不是systemd管理的或者进程会fork出很多子进程这时用cgroup更灵活。mkdir /sys/fs/cgroup/cpu/limit-group echo 200000 /sys/fs/cgroup/cpu/limit-group/cpu.cfs_quota_us echo 100000 /sys/fs/cgroup/cpu/limit-group/cpu.cfs_period_us echo pid /sys/fs/cgroup/cpu/limit-group/tasks这段配置的含义是在每个100ms的周期内该组进程最多使用200ms的CPU时间相当于2个核心。需要注意写入tasks的PID如果退出限制就自动解除通常需要配合一个简单脚本定期检查进程是否存在。4.3 临时限流的土办法调整Nginx并发连接数如果高负载是突发流量引起的应用层又来不及扩容那就在入口处限制连接速率。Nginx的limit_req模块可以做到限流limit_req_zone $binary_remote_addr zonereq_limit:10m rate10r/s; server { location /api/ { limit_req zonereq_limit burst20 nodelay; proxy_pass http://backend; } }rate10r/s表示每个客户端IP每秒最多10个请求burst20允许短时间内的突发量。这能防止某一个来源的请求把后端资源打满。注意这是临时手段限流同时要尽快找出流量来源和业务逻辑的问题。5. 方法三从JVM、数据库、Web服务器三个层面做调优很多服务器负载高其实是配置参数和实际资源不匹配导致的。默认配置能满足小流量场景但流量一旦上来各种排队和竞争就会出现造成卡顿和假死。5.1 JVM堆内存和GC策略调整Java应用负载过高的常见内部原因是GC频繁触发Full GC导致STW停止世界卡顿。先加上GC日志再观察-Xms4g -Xmx4g -XX:UseG1GC -XX:MaxGCPauseMillis200 -XX:PrintGCDetails -XX:PrintGCDateStamps -Xloggc:/var/log/gc.logG1垃圾回收器适合大堆内存场景目标是控制最大暂停时间。如果老年代内存涨到一定阈值才触发GC但业务对象分配过快就会形成GC线程消耗大量CPU应用线程却拿不到CPU的局面。判断方法很简单看GC日志如果Full GC频繁且每次时间超过1秒基本可以确定堆内存配置有问题要么调大堆空间要么优化业务代码减少大对象分配。还有一个常见坑是-Xms和-Xmx不相等导致JVM动态扩容时卡顿。我遇到过几次类似现象堆从2G慢慢涨到6G每次扩容都伴随一段卡顿后来直接把初始堆和最大堆设成相同值垃圾回收反而更平稳了。5.2 MySQL连接数和缓存池优化数据库服务器负载过高最常见的原因不是SQL有多烂而是连接数被打满新请求排队等待。MySQL的默认配置max_connections是151但很多小团队不调这个参数应用连接池却配置了两三百个连接。连接一旦超过限制客户端就会一直重试或报错。先查当前连接状态SHOW STATUS LIKE Threads_connected; SHOW STATUS LIKE Max_used_connections; SHOW VARIABLES LIKE max_connections;如果Threads_connected长期贴近max_connections就需要调整set global max_connections500;但光调大不行还要看innodb_buffer_pool_size。这个参数决定InnoDB缓存表和索引数据的内存空间一般建议设置为物理内存的70%左右但不能超过太多否则会跟操作系统本身的内存需求打架。之前一些优化方案作为参考8G内存的数据库服务器innodb_buffer_pool_size设置为5G~6G比较合理。开启innodb_buffer_pool_load_at_startup和innodb_buffer_pool_dump_at_shutdown减少重启后的预热时间。慢查询日志开启但输出到独立文件slow_query_log ON slow_query_log_file /var/log/mysql-slow.log long_query_time 25.3 Nginx worker进程和文件句柄Nginx的高负载问题多数出在文件句柄耗尽或worker进程数配置不合理。worker_processes auto; worker_rlimit_nofile 65535; events { worker_connections 10240; }关于worker进程数有人会配置成CPU核数的好几倍这不是最优做法auto让Nginx按CPU核数自动配置简单直接。worker_rlimit_nofile和worker_connections则决定了单进程可以打开的文件和连接数。配合系统参数ulimit -n 65535以及/etc/security/limits.conf* soft nofile 65535 * hard nofile 65535这套组合在大多数Linux发行版都适用。调整完之后别忘了nginx -s reload不是restart这样才能平滑加载配置。6. 方法四内存、Swap与磁盘I/O的专项治理这一节要处理的是“看起来内存够用但卡得要死”的典型场景。很多服务器负载高和业务逻辑无关纯粹是系统资源调配出了问题。6.1 Swap过度使用的必然结果服务器物理内存16G应用只用了12Gswap却占到4G是这种情况的典型外观。应用进程的内存被换到磁盘上每次访问这些内存都要经过磁盘读写速度差距通常在几个数量级整体性能自然一落千丈。先查Swap用量free -h swapon --show再看哪个进程在大量使用Swapfor f in /proc/*/status; do awk /VmSwap/{swap$2} /Name/{name$2} END{if(swap0) print name, swap} $f 2/dev/null done | sort -k2 -n -r | head -20排查之后如果是某些进程确实需要更多内存优先增加物理内存或调整应用的堆大小而不是依赖Swap兜底。如果无法加内存可以考虑把vm.swappiness调低让系统尽量少用Swapsysctl vm.swappiness10 echo vm.swappiness10 /etc/sysctl.conf这里简单解释一下swappiness的取值范围是0~100值越大越积极使用Swap。改成10之后系统只在内存非常紧张时才使用Swap对大多数业务场景都比较友好。6.2 目录缓存与inode耗尽问题有一种负载高是“假象”磁盘I/O正常CPU正常内存也正常但服务就是卡。这时候不妨查一下inode是否耗尽df -idf -i显示的是inode使用情况不是磁盘空间。如果某个分区的IUse%达到100%即使磁盘还有大量剩余空间也无法创建新文件了。小文件特别多的程序日志、缓存文件、消息队列临时文件很容易触发这个问题。解决方法是清理过期日志然后对文件系统做合理规划比如日志目录独立分区、定期归档压缩。6.3 日志文件太大引发的连锁卡顿日志文件没做切割越写越大达到几个GB甚至几十个GB会导致磁盘I/O持续升高。很多程序在写日志时会打开文件并写入尾部但如果大量程序同时写同一个文件文件锁竞争也会消耗大量CPU。养成好习惯使用logrotate做日志切割。配置示例/var/log/nginx/*.log { daily rotate 7 missingok compress notifempty sharedscripts postrotate /bin/kill -USR1 cat /run/nginx.pid 2/dev/null 2/dev/null || true endscript }每天切割一次保留7份压缩历史日志。对于Nginx这类应用切割后要用USR1信号重新打开日志文件否则日志会继续往已经删掉的文件里写。这一步操作内容看起来简单但能避免很多莫名其妙的磁盘和I/O问题。7. 方法五应用层语义优化与集群横向扩容前面的方法都算“单机优化”如果这些都做完了负载问题依然周期性爆发就需要考虑应用层的优化逻辑和架构层面的横向扩容了。7.1 慢查询和热点逻辑的排查在应用层面很多负载问题其实来自慢查询拖垮数据库数据库拖垮整个系统。所以典型的排查路径是拿到慢查询日志看哪些SQL拖了后腿。mysqldumpslow -s at -t 20 /var/log/mysql-slow.log按平均查询时间排序找出最耗时的20条SQL。得到结果后别急着加索引先用EXPLAIN查看执行计划EXPLAIN SELECT * FROM orders WHERE user_id123 AND status1 ORDER BY created_at DESC;重点看type字段如果是ALL全表扫描基本能确定需要优化索引如果是index说明已经在用索引但可能效率不高需要看是否覆盖了所有查询条件。再检查rows字段估算扫描的行数和实际返回数据量对比一下你大概能判断出索引选择是否合理。我在一次排查中遇到的场景是电商订单表数据量几千万运营后台筛选订单时每次查询都全表扫描单条SQL跑了十几秒业务高峰期这个查询一执行数据库CPU负载直接打满。后来加了(user_id, status, created_at)联合索引单条SQL降到几十毫秒问题彻底消失。7.2 缓存热Key击穿的场景化解法另一个常见问题是缓存设计不合理。Redis缓存命中率高但某个热点Key的缓存一旦过期瞬间有大量请求同时去查数据库把数据库打挂。处理思路很直接热点Key的过期时间加上随机值避免同一时间集体失效或者对没有命中的请求加互斥锁只放一个线程去查库其他线程等待结果再就是热点数据干脆不设置过期时间由后台任务定期更新。// 伪代码示例互斥锁重建缓存 if (cache.get(key) null) { if (lock.tryLock(key)) { try { Object value db.query(key); cache.set(key, value); } finally { lock.unlock(key); } } else { Thread.sleep(50); return cache.get(key); } }这段代码的逻辑好理解缓存未命中时先尝试获取分布式锁拿到锁的线程负责查数据库并回填缓存其他线程短暂等待后再次读缓存。核心价值是避免所有请求同时涌向数据库。7.3 横向扩容的时机与前提单机优化终究有上限。当CPU长期占用超过70%或负载持续高于核数两倍以上且确认代码层面没有明显优化空间时就该考虑横向扩容了。但扩容不是简单加机器。前提是应用需要支持无状态横向扩展会话数据要放到Redis或数据库本地缓存要可以容忍丢失上传的文件要放到对象存储或共享存储。否则扩再多的机器也解决不了问题反而增加运维复杂度。扩容时建议在负载均衡层面配置健康检查和权重分配。比如Nginx的upstream配置upstream backend { least_conn; server 10.0.0.1:8080 max_fails3 fail_timeout30s; server 10.0.0.2:8080 max_fails3 fail_timeout30s; keepalive 32; }least_conn会把新请求分配给当前连接数最少的节点更适合高并发场景下的负载均衡。keepalive 32是Nginx到后端的长连接数量能减少重复握手带来的耗时和资源占用。8. 常见问题与排查技巧实录最后把这些年实际踩过的坑集中梳理一下。很多问题看着复杂其实就是某个小细节没注意到。8.1 CPU占用不高但负载长期偏高到底查什么这种问题最容易让人一头雾水。我的排查经验是从三个方向切入第一看D状态进程ps -eo pid,stat,cmd | grep DD状态是不可中断睡眠说明进程在等待I/O第二看网络层的连接状态ss -s查看socket统计频繁重传或大量TIME_WAIT会导致网络子系统处理变慢第三看内存回收机制/proc/pressure/memory里的avg值如果偏高说明内存回收压力大系统在花大量时间做内存回收。8.2 为什么重启后负载降了但半小时后又升回去这种情况基本可以断定是业务流量或应用逻辑存在必然的周期性问题。比如每天定时任务在整点集中执行比如用户访问高峰期集中在晚上八点。解决办法是在业务高峰前提前扩容或者把定时任务错峰分散到不同时间点。也可以用crontab的随机延迟来打散任务30 2 * * * sleep $((RANDOM%60)); /opt/scripts/cleanup.sh这行配置的意思是每天凌晨2点30分执行清理脚本但实际执行时间在2点30分到2点30分59秒之间随机。多个服务器配置不同的随机范围就能明显降低定时任务集中触发带来的负载尖峰。8.3 云服务器的“稳定”假象公有云上有一个比较隐蔽的问题CPU积分制。某些云实例类型使用CPU积分机制你把CPU跑满一段时间后积分耗尽CPU就会被强制限制在一个较低的水准。表现就是“温度”不高但性能突然变差。如果发现服务器负载没有明显变化但业务卡顿加剧可以看看云平台监控里的CPU使用率和积分情况。这一点在物理机上不存在但在云上非常值得关注。8.4 排查高负载时的安全注意事项服务器负载过高时做任何操作都要更谨慎。第一不要同时打开太多终端窗口执行命令高负载下每个shell连接都会消耗系统资源第二谨慎使用kill -9尤其是杀掉数据库、Redis等核心进程时可能会引发数据丢失或长时间恢复第三避免在生产环境直接修改配置文件后立即重启尽量先备份配置评估影响范围。我在实际工作中遇到过因为重启顺序问题导致的事故先重启了应用没等它起来就重启了数据库结果应用连接数据库失败启动失败业务中断时间比预期长了很多倍。正确的顺序应该是先确认数据库健康再启动应用如果应用是多节点的逐个节点重启而不是全部一起重来。其实高负载问题很多是“日常不维护出事才救火”的后果。如果平时就做好监控告警、日志切割、参数调优大部分卡顿和宕机根本不会发生。这套方法论我用了几年最直接的感受是处理问题的速度越快留给你分析根因的时间就越多问题就越不容易反复。下次你再遇到load average飙高按照“先诊断、后止损、再调优、终扩容”的顺序走多半能省掉不少折腾的时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑