4、内存与缓存优化:内存分配策略调优、缓存命中率提升、Swap分区优化

内存和缓存,说白了就是系统的「快车道」和「临时仓库」。很多系统跑着跑着就慢了,不是CPU不行,而是内存分配策略不对,缓存命中率太低,或者Swap分区在拖后腿。我见过太多团队花大价钱升级硬件,结果调一下参数就能解决的问题,硬是没人去碰。

这一章,咱们就聊聊怎么从这三个维度把内存性能榨干。

4.1 内存分配策略调优

Linux内核默认的内存分配策略,其实挺「保守」的。它倾向于尽量少用Swap,尽量把物理内存留给进程。但有些场景下,这种保守反而成了瓶颈。

4.1.1 vm.overcommit 与 overcommit_ratio

先看一个关键参数:vm.overcommit_memory。它控制着内核是否允许「超额分配」内存。

  • 0:默认策略。内核会估算一下,如果觉得内存不够,就拒绝分配。这其实挺坑的,有些应用申请内存时只是「预留」,实际用不了那么多,结果被内核误杀。
  • 1:总是允许超额分配。不管物理内存够不够,先答应再说。适合那些内存占用波动大的应用,比如数据库的查询缓存。
  • 2:严格限制。不允许超过 overcommit_ratio 设定的比例。这个最安全,但也最容易导致OOM。

我个人习惯,在内存充足的服务器上,直接设成1:

echo 1 > /proc/sys/vm/overcommit_memory

但要注意,设成1之后,你得保证系统有足够的Swap或者OOM Killer能兜底。否则,内存泄漏的程序会把系统拖垮。

警告: 我曾经在一个内存只有4G的旧服务器上设了overcommit=1,结果一个Java进程申请了8G内存,系统直接卡死。所以,设成1之前,先评估好你的内存水位。

4.1.2 vm.min_free_kbytes 与内存碎片

另一个容易被忽略的参数是 vm.min_free_kbytes。它决定了内核保留多少「空闲内存」用于紧急分配。值太小,内存碎片严重时,大块内存分配会失败;值太大,又浪费内存。

我一般这样算:

# 总内存的 0.5% 到 1% 之间
echo 65536 > /proc/sys/vm/min_free_kbytes  # 64MB,适合8G内存的机器

怎么判断是不是太小了?看 /proc/buddyinfo。如果里面全是小块的(比如2^0、2^1),没有大块(2^9以上),说明碎片严重,该调大 min_free_kbytes 了。

小技巧: 调完 min_free_kbytes 后,可以用 cat /proc/buddyinfo 观察一下。如果连续几次都看不到大块连续内存,那就再加一点。

4.2 缓存命中率提升

缓存命中率,是衡量内存效率的核心指标。说白了,就是「我要的数据,是不是已经在缓存里了?」。命中率高,系统就快;命中率低,就得去磁盘或者网络拿,那速度差了几个数量级。

4.2.1 文件缓存与脏页回写

Linux的文件缓存(Page Cache)是提升I/O性能的关键。但脏页(Dirty Pages)回写策略,直接影响缓存效率。

关键参数:

参数 默认值 推荐值(SSD) 说明
vm.dirty_ratio 20% 10% 脏页占内存的最大比例,超过后强制回写
vm.dirty_background_ratio 10% 5% 后台开始回写的阈值
vm.dirty_expire_centisecs 3000(30秒) 3000 脏页超过这个时间,必须回写

为什么推荐调低?因为默认值是为了照顾机械硬盘。SSD的随机写性能好很多,没必要攒那么多脏页。攒多了,一旦触发强制回写,瞬间I/O压力会很大。

我一般这样调:

echo 10 > /proc/sys/vm/dirty_ratio
echo 5 > /proc/sys/vm/dirty_background_ratio

4.2.2 应用层缓存策略

系统层的缓存调完了,应用层也得跟上。我见过最典型的例子,是Redis的缓存命中率从85%掉到40%,原因是key过期策略没调好。

几个实用原则:

  • 热点数据不过期:对于频繁访问的数据,设置较长的TTL,或者干脆不过期,用LRU淘汰。
  • 缓存粒度要细:别把整个页面缓存起来,缓存单个数据块。这样命中率更高,更新也更灵活。
  • 预热要到位:系统重启后,缓存是空的。这时候流量打进来,全是Miss。我习惯在启动脚本里加一段预热逻辑,把热点数据先加载到缓存。
核心观点: 缓存命中率提升,不是靠一个参数搞定的。系统层调参数,应用层改策略,两层配合才能见效。

4.3 Swap分区优化

Swap,很多人觉得是「性能毒瘤」。其实不然。合理使用Swap,反而能提升系统的稳定性。关键是怎么用。

4.3.1 swappiness 的真相

vm.swappiness 这个参数,很多人理解错了。它不是「Swap的使用比例」,而是「内核倾向于使用Swap的程度」。范围0-100,默认60。

  • 0:尽量不用Swap,除非物理内存彻底耗尽。
  • 100:非常积极地把内存页换到Swap。
  • 10-30:适合大多数服务器,只在内存压力较大时才用Swap。

我个人的建议:

echo 10 > /proc/sys/vm/swappiness  # 服务器场景

为什么是10?因为默认的60,会让内核在内存还有不少空闲时就开始换页,这其实没必要。调低之后,内核会更倾向于保留物理内存中的页,减少不必要的Swap I/O。

注意: 别把 swappiness 设成0。我曾经在一台数据库服务器上设了0,结果内存泄漏时,OOM Killer直接把数据库进程杀了。设成10,至少给系统留点缓冲。

4.3.2 Swap分区大小与位置

Swap分区多大合适?老规矩是「物理内存的2倍」,那是给内存只有几百兆的年代用的。现在内存动辄64G、128G,2倍太浪费了。

我一般这样算:

物理内存 Swap大小 说明
< 8G 物理内存的 1-2 倍 内存小,Swap作为重要补充
8G - 32G 物理内存的 0.5-1 倍 够用就行,别浪费磁盘空间
> 32G 16G - 32G 主要用来兜底,防止OOM

另外,Swap分区的位置也很重要。如果系统有多个磁盘,把Swap放在最快的磁盘上(比如NVMe SSD),能显著提升换页速度。

4.3.3 Swap与内存回收的联动

最后说一个容易被忽略的点:Swap和内存回收(kswapd)是联动的。当内存压力增大时,kswapd会尝试回收内存页。如果回收不积极,就会触发Swap。

关键参数是 vm.vfs_cache_pressure。它控制着内核回收dentry和inode缓存的积极程度。默认100,值越大回收越积极。

我一般这样调:

echo 200 > /proc/sys/vm/vfs_cache_pressure  # 更积极地回收文件系统缓存

这样做的目的是:让内核优先回收文件系统缓存,而不是把进程的内存页换到Swap。毕竟,文件系统缓存丢了,大不了重新读盘;进程内存页丢了,那性能损失就大了。

避坑指南: 我曾经在一个NFS服务器上调高了 vfs_cache_pressure,结果文件系统缓存被频繁回收,导致NFS客户端性能下降。所以,这个参数要根据场景来调,不是越高越好。

本章小结:内存优化的核心逻辑

说了这么多,其实就三句话:

  • 分配策略:别让内核太保守,也别太激进,找到平衡点。
  • 缓存命中:系统层调参数,应用层改策略,两层配合。
  • Swap优化:不是不用,而是用得聪明,让它当「保险」,不当「累赘」。

下面这张图,把这三者的关系画清楚了:

内存与缓存优化核心逻辑 内存性能 内存分配策略 缓存命中率 Swap分区优化 overcommit / min_free_kbytes 内存碎片控制 脏页回写策略 应用层缓存策略 swappiness 调优 分区大小与位置 三者联动,缺一不可 分配策略是基础,缓存命中是核心,Swap是最后防线

嗯,这一章的内容就到这儿。调参数不难,难的是理解背后的原理。你把这些原理吃透了,下次遇到内存问题,就不会只想着「加内存」了。