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能兜底。否则,内存泄漏的程序会把系统拖垮。
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。
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。毕竟,文件系统缓存丢了,大不了重新读盘;进程内存页丢了,那性能损失就大了。
vfs_cache_pressure,结果文件系统缓存被频繁回收,导致NFS客户端性能下降。所以,这个参数要根据场景来调,不是越高越好。
本章小结:内存优化的核心逻辑
说了这么多,其实就三句话:
- 分配策略:别让内核太保守,也别太激进,找到平衡点。
- 缓存命中:系统层调参数,应用层改策略,两层配合。
- Swap优化:不是不用,而是用得聪明,让它当「保险」,不当「累赘」。
下面这张图,把这三者的关系画清楚了:
嗯,这一章的内容就到这儿。调参数不难,难的是理解背后的原理。你把这些原理吃透了,下次遇到内存问题,就不会只想着「加内存」了。