跑过生产环境的朋友大概都遇到过这种场景:半夜两点突然被告警短信炸醒,打开一看CPU飙到95%,内存吃紧,磁盘IO延迟几十毫秒,数据库响应慢得像是在拨号上网。手忙脚乱一通操作,问题好像解决了,但心里清楚——下次还会来。
性能优化这事儿,说难也难,说简单也简单。难在你得搞清楚瓶颈到底在哪,简单在Linux早就把工具给你备齐了,只是很少有人系统地捋一遍。今天这篇,咱们就把CPU、内存、网络、磁盘IO、文件系统这五大块的排查思路和调优方案,从头到尾理清楚。
这是「Linux运维系列教程」的第七期。前面的权限管理、文件查找、文本处理如果你还没看,回头翻翻前面的文章。今天这篇,建议收藏,遇到问题的时候翻出来对照着排查。
一、CPU优化:找到那个在偷跑的核心
CPU的问题,说白了就是两个:要么是某个进程在疯狂占用,要么是上下文切换太频繁导致有效计算时间被稀释。排查的关键在于——别上来就杀进程,先看数据。
1.1 用top看全局,用mpstat看细节
top是最直观的工具,但它给的是整体平均值。一台4核机器,如果只有一个核被跑满,top显示的总体使用率可能只有25%,你会误以为问题不大。这时候需要用mpstat看每个核的独立数据:
mpstat -P ALL 1 3
这个命令每秒刷新一次,连续输出3次,分别展示每个CPU核心的用户态使用率(%usr)、系统态使用率(%sys)、IO等待(%iowait)和空闲率(%idle)。看上面这张截图:
注意CPU 0的%usr高达58%,%idle只剩20%,而其他三个核都在45%左右的空闲。这说明有个单线程程序在CPU 0上跑满了。这时候再用pidstat定位具体进程:
pidstat -u 1 2
pidstat会按进程展示CPU占用。上面截图里nginx占了33.5%的CPU,mysqld占18%,java占12%。如果是生产环境,nginx这个占比说明你的并发量不小,可能需要考虑加worker进程数或者做负载均衡。
1.2 上下文切换的隐形杀手
CPU使用率不高,但系统就是慢?十有八九是上下文切换(context switch)在捣鬼。每个进程切换一次上下文,CPU就要保存和恢复寄存器、页表、TLB缓存,这些都是时间开销。
vmstat 1 5
看vmstat输出的cs列(context switch)。正常情况下,每秒几千次是正常的。如果到了上万甚至几十万,说明有线程在疯狂创建和销毁,或者锁竞争太严重。一个真实案例:有台服务器cs飙到20万,排查发现是一个日志框架每次写日志都创建新线程,改成线程池之后cs降到3000,响应时间从200ms降到15ms。
1.3 调优建议
• 合理设置进程/线程的CPU亲和性(taskset),把关键进程绑定到特定核上
• 调整nginx的worker_processes为auto,让它自动匹配CPU核心数
• 数据库连接池大小别设太大,太多连接反而增加上下文切换开销
• 关闭不必要的IRQ中断迁移,echo 0 > /proc/irq/N/smp_affinity
• 对于高IO负载的场景,可以考虑开启CPU频率调节器的performance模式
二、内存优化:不是用得越少越好
很多人看到free显示内存用了90%就紧张,实际上Linux的内存管理和Windows完全不是一个思路。Linux的原则是:空闲的内存就是浪费的内存。所以它会把空闲内存拿来做buffer和cache,加速文件读写。关键指标不是used,而是available。
2.1 free的正确读法
free -h
看上面的截图:total是15G,used是8.2G,free只有3.1G。乍一看好像内存快满了。但注意available那列——6.5G。这意味着真正可用的内存还有6.5G(free + buff/cache中可回收的部分)。所以实际内存压力并不大。
buff/cache那4G不是被"吃掉"了,是系统拿来缓存磁盘数据的。当应用程序需要内存时,系统会自动释放这部分缓存。所以别没事就echo 3 > /proc/sys/vm/drop_caches,这不会让你的系统变快,只会让磁盘IO瞬间飙升。
2.2 Swap的使用是危险信号
上面截图中Swap用了1.2G,说明系统曾经经历过内存紧张,不得不把部分内存页交换到磁盘。Swap一旦使用,性能会断崖式下降,因为磁盘的随机读写比内存慢几个数量级。
判断内存压力的黄金指标是vmstat的si(swap in)和so(swap out)列:
vmstat 1 5
如果si和so持续不为0,说明系统在频繁交换,这是内存不足的明确信号。解决办法不是调参数,而是加内存或者优化应用的内存占用。
2.3 调优建议
• 调整vm.swappiness参数:默认60,建议设为10-20,减少系统主动使用swap的倾向
• 为Java应用设置合理的-Xmx和-Xms,避免JVM吃光所有内存
• MySQL的innodb_buffer_pool_size建议设为物理内存的60-70%
• 使用cgroup限制容器内存上限,防止单个容器OOM影响整台机器
• 开启Transparent Huge Pages(THP)要谨慎,对某些数据库反而有负面影响
三、网络优化:连接数不是越多越好
网络性能问题最常见的表现是:连接超时、请求响应慢、TCP重传率高。排查网络问题和排查CPU一样,先看全局,再定位到具体的连接和进程。
3.1 用ss/netstat看连接状态
ss -s
ss -s给出的是网络连接的总览。TCP连接1523个,其中已建立892个,已关闭156个,孤儿连接12个,TIME_WAIT状态128个。几个关键指标需要关注:
TIME_WAIT数量过多,说明短连接太频繁。每个TIME_WAIT连接会占用一个本地端口,默认端口范围是32768-60999,大约28000个端口。如果TIME_WAIT堆到几万个,新连接就会因为端口耗尽而失败。解决办法是开启tcp_tw_reuse(注意不是tcp_tw_recycle,后者在NAT环境下会出问题):
sysctl -w net.ipv4.tcp_tw_reuse=1
CLOSE_WAIT数量异常则说明应用程序没有正确关闭连接。上面截图中CLOSE_WAIT有128个,这意味着有128个连接远端已经断开,但本地应用还没调用close()。这通常是代码层面的bug,需要检查连接池管理。
3.2 内核参数调优
高并发场景下,默认的TCP参数往往不够用。一组经过生产验证的参数:
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 6
net.core.netdev_max_backlog = 65535
net.ipv4.ip_local_port_range = 1024 65535
解释一下核心参数:somaxconn控制监听队列的最大长度,默认128在高并发下根本不够用;tcp_max_syn_backlog是半连接队列大小,防SYN Flood的同时也影响正常连接建立速度;tcp_fin_timeout控制FIN_WAIT2的超时时间,从默认60秒降到15秒可以更快释放连接。
3.3 调优建议
• 使用连接池复用TCP连接,避免频繁创建和销毁
• 开启tcp_tw_reuse复用TIME_WAIT连接,但别开tcp_tw_recycle
• 对于内网服务间调用,考虑使用HTTP/2的多路复用减少连接数
• 监控TCP重传率(ss -ti),重传率超过3%就该排查网络链路了
四、磁盘IO优化:别让硬盘拖了后腿
磁盘IO往往是整个系统中最慢的一环。一块机械硬盘的随机读写延迟大约10ms,SSD大约0.1ms,而内存的延迟是纳秒级别。所以任何涉及磁盘的操作,都是性能优化的重点区域。
4.1 用iostat看清IO瓶颈
iostat -dx 1 3
iostat -dx给出的是每个磁盘设备的详细IO统计。重点看这几列:
%util:磁盘利用率。超过80%就说明磁盘接近饱和。上面截图中nvme0n1的%util达到78.5%,说明这块NVMe SSD已经很忙了。如果%util到100%,磁盘就成了绝对瓶颈,后面的请求只能排队等待。
r_await和w_await:读写请求的平均等待时间(毫秒)。sda的w_await是8.35ms,对于机械硬盘来说偏高;nvme0n1的w_await只有1.25ms,这是SSD该有的水平。如果你的SSD等待时间超过5ms,那要么硬盘快坏了,要么队列深度太大。
aqu-sz:平均队列长度。大于1说明有请求在排队。nvme0n1的aqu-sz是1.05,说明几乎每次IO都要等前面一个完成。
4.2 用iotop找到IO大户
iotop -b -n1 -o
iotop按进程展示磁盘IO。上面截图里nginx读写各十几M/s,mysqld写入15.6M/s。如果你的日志写入量特别大,考虑几个优化方向:异步写日志、减少日志级别、把日志单独放到一块磁盘上。
4.3 调优建议
• 日志和数据分盘存放,避免互相争抢IO带宽
• 数据库开启O_DIRECT绕过页缓存,减少双重缓存的内存浪费
• 调整IO调度器:SSD用mq-deadline或none,HDD用bfq或kyber
• 增加vm.dirty_ratio和vm.dirty_background_ratio,让系统批量写盘而不是频繁写
• 对于高写入场景,考虑RAID 10或者NVMe SSD替代机械盘
五、文件系统优化:选对格式事半功倍
文件系统的选择往往被忽视,但它对性能的影响是基础性的。ext4稳定但性能平平,XFS在大文件和高并发写入场景下表现更好,btrfs和ZFS提供了快照和压缩但会带来一定的性能开销。
5.1 查看文件系统类型和状态
df -Th
df -Th展示各分区的文件系统类型和使用情况。注意/var/log用了77%,日志分区快满了。日志分区满了会导致服务无法写入日志,严重时甚至让系统无法正常启动。建议设置logrotate自动清理,或者用tmpfs挂载不重要的日志目录。
5.2 ext4与XFS的关键参数
ext4的保留块比例默认是5%,对大分区来说这是很大的浪费。一块500G的分区,5%就是25G。如果你的分区只存数据不跑系统,可以把保留比例降到1%:
tune2fs -m 1 /dev/sdb1
XFS挂载参数noatime可以显著减少写入:默认情况下每次读取文件都会更新atime(访问时间),这会产生额外的写IO。加上noatime后,读文件不再写元数据:
mount -o remount,noatime /data
5.3 Inode耗尽的坑
很多人遇到过磁盘空间明明还有50%,但就是无法创建文件的情况——Inode用完了。每个文件、目录、软链接都要消耗一个Inode。小文件特别多(比如缓存目录、session文件)的场景最容易中招:
df -i
如果IUse%接近100%,赶紧清理小文件。格式化时可以指定更大的Inode比例(-i参数),但一旦格式化完成就无法调整了。
5.4 调优建议
• 数据库和日志分区使用XFS,大文件和高并发写入性能优于ext4
• 所有分区挂载都加noatime参数,减少不必要的元数据写入
• 定期执行e2fsck(ext4)或xfs_scrub(XFS)检查文件系统健康
• 小文件密集型场景可以考虑tmpfs,把数据放内存里,重启不保留
• 用df -i定期监控Inode使用率,别等到100%才发现问题
总结一下
性能优化不是一上来就改参数,而是先测量、再分析、最后调整。CPU看mpstat和pidstat,内存看free和vmstat,网络看ss和netstat,磁盘看iostat和iotop,文件系统看df和tune2fs。每个工具都不是孤立的,它们互相印证,帮你定位真正的瓶颈。
记住一条原则:不要在没有数据支撑的情况下盲目调参。参数调得再漂亮,如果瓶颈根本不在这儿,那都是白费功夫。