服务器入坑指北:脏页水位与回写节奏
写入一卡一卡的;不知道脏页到了多少会开始回写、多少会把写入卡住;调了比例却没感觉。
在你动手调参数之前,先把三条前提看清:① 脏页有两条线,不是一条——后台线越过后内核开始在后台回写,前台线越过会把写入逼成同步等待;② "可用内存"要用MemAvailable,不要用空闲量(free的free列与available列实测可差一个量级,用错会让"上限"算得过小);③ 比例组与字节组是互斥的——现读时只有一个非零,别当成两个独立的旋钮。
这篇给三段内容:读两条线与可用内存(只读,可直接粘贴)→ 算上限(只读,可直接粘贴)→ 看节奏与改回——最后一段里的"改参数"是写操作:需要相应权限、且/proc/sys可写(详见那一段的前提),标回滚点。取值用占位符,参数名、字段名与计数器名照写。
两条线各是什么
- 后台线(
vm.dirty_background_ratio):越过它,内核开始在后台回写——这时写入还不卡; - 前台线(
vm.dirty_ratio):越过它,写入被迫同步等待。
判据:先看Dirty落在哪一段,再谈"为什么卡"——只看一条线必然把两类现象混成一种。两条线之间那一段的判据是:Dirty落在两线之间时,回写已经在跑(bo与wtps已非零),而应用还没被阻塞。
读两条线与可用内存块
sysctl vm.dirty_background_ratio # 后台水位线:口径是百分数(要 ÷100)
sysctl vm.dirty_ratio # 前台水位线:口径同样是百分数
sysctl vm.dirty_background_bytes # 另一套口径:按字节设后台闸门
sysctl vm.dirty_bytes # 字节组的前台闸门:与比例组互斥
grep -E '^Dirty:|^Writeback:' /proc/meminfo # 现读脏页与在途回写量(用 ERE 的竖线,别加反斜杠)
cat /proc/meminfo # "可用内存"从这里读 MemAvailable
free -m # 对照:只看 available 列,不要看 free 列
判据与单位:每条线都要认领自己的口径——比例组是百分数,字节组是字节;grep -E '^Dirty:|^Writeback:' /proc/meminfo 的量纲写在行里(是 kB),而 kB 是 1024 字节、不是 1000;cat /proc/meminfo 里算式要用的那一项是 MemAvailable。
算上限
几个读入口的输出形态(值用占位符,形态是照敲后看到的):
# sysctl 的形态是"名 = 值":
vm.dirty_ratio = <比例>
vm.dirty_bytes = <字节数>
# /proc/meminfo 的形态是"字段: 值 kB"(kB 是 1024 字节):
MemAvailable: <值> kB
MemFree: <值> kB
Dirty: <值> kB
Writeback: <值> kB
第一式:脏页上限 ≈ dirty_ratio × 可用内存 ÷ 100。
÷100 这一步不能省——比例是百分数,省了会差 100 倍。
算一遍(数值为演示值):MemAvailable 按 8388608 kB(也就是 8 GiB,按 kB = 1024 B 换算)、dirty_ratio 按 10(百分数)⇒ 脏页上限 ≈ 10 * 8388608 / 100 = 838860.8(kB);换成 MiB:838860.8 / 1024 ≈ 819.2(MiB)。
第二式:两条水位线的关系——后台线 < 前台线。
接着算(同一组数往下走):vm.dirty_background_ratio 按 2 ⇒ 后台线 ≈ 2 * 8388608 / 100 = 167772.16(kB),也就是 167772.16 / 1024 ≈ 163.84(MiB)小于前台线的 819.2 MiB——两条线的关系成立。
判据:把上限和你实际的写入峰值比——峰值远大于上限 ⇒ 一定会撞前台线;峰值落在两线之间 ⇒ 你能看到回写在跑,但写入还不卡。
互斥这一条要在改之前用现读确认:vm.dirty_bytes 非零时,生效的是字节组,比例组不参与——所以我按现读核对过:“现读时两者只有一个非零”;改之前先读一次,看清现在生效的是哪一组。
看节奏的两组口径
vmstat <间隔> <次数> # 判据列是 bo(每秒写出块数)
sar -b <间隔> <次数> # 另一口径:判据列是 wtps 与 bwrtn/s(需先装 sysstat)
sync # 手动把脏页推出去(无选项=全部)
sync -f <路径> # 只推指定路径所在的文件系统,缩小影响面
判据(同一现象,单位不同,引用时必须写清用的是哪一个):
vmstat <间隔> <次数>的bo列是每秒写出的块数;它的首行是自启动以来的平均值,不是采样值——从第二行起才是区间值;sar -b <间隔> <次数>的wtps(每秒写请求数)与bwrtn/s(每秒写出字节数)是同一件事的另一口径。
节奏参数还有两个:sysctl vm.dirty_expire_centisecs(脏页过期后强制写回)与sysctl vm.dirty_writeback_centisecs(回写线程唤醒间隔)——单位就写在参数名里:centisecs是"百分之一秒"(具体量纲措辞以现读与文档条目为准);把它当"秒"会差 100 倍。
取舍:放宽、收紧,还是换一组
- 放宽(上限更高):写入更少被卡;代价是掉电窗口变大、一次性回写更猛;
- 收紧:回写更平缓;代价是回写更频繁,写入更容易被强制同步;
- 改字节组:可以按绝对量定闸门;代价是与比例组互斥,要整组切换。
三条代价都要写出来。判据:先问"这块盘的写入峰值有多大"和"掉电我能丢多少"。
应用层也要分清分工:fsync是"保证落盘"(把文件的内存态与存储同步),sync_file_range只回写文件的一段区间,而O_SYNC是打开即要求同步写——它们与本层的水位线分工不同:一个管保证、一个管节奏。
改与读回块
sysctl vm.dirty_ratio # 改前:记录原值(回滚依据)——只读,谁都能读
sysctl vm.dirty_background_ratio # 改前:两条线都要记——只读
sysctl -w vm.dirty_ratio=<百分数> # 改前台线:需 root 或 CAP_SYS_ADMIN,且 /proc/sys 可写
sysctl -w vm.dirty_background_ratio=<百分数> # 改后台线:权限前提同上
sysctl vm.dirty_bytes # 读回:确认生效的是哪一组(互斥关系)——只读
grep -E '^Dirty:|^Writeback:' /proc/meminfo # 读回:脏页与在途回写量的量级变了没有——只读
写之前先把三件事说清:
- 权限:
sysctl -w需要 root 或CAP_SYS_ADMIN——普通身份敲下去是权限不足; - 只读环境:受限容器里
/proc/sys可能被挂成只读(我这台就是:该文件属 root、连根身份都写不进去)——这时你会看到两种不同含义的报错:sysctl -w …报权限不足,而直接echo <值> > /proc/sys/vm/<参数>报 Read-only file system(一个是没权限,一个是文件系统本身写不进去); - 持久化:
sysctl -w只改运行值,重启即失——要跨重启就写/etc/sysctl.d/下的配置文件,再用sysctl -p让系统重新读取。
只读环境里的替代动作:把只读那几段照跑一遍(读两条线、读MemAvailable、算上限、看bo与wtps),把"该改成多少"算出来、写成一条待办交给有权限的人——读得到、改不了,不是白跑一趟。
现象到动作的判定表
| 现象 | 结论 | 动作 |
|---|---|---|
| 写入一卡一卡 | 撞前台线 | 比写入峰值与上限;评估放宽或收紧 |
| 盘一直有回写但写入不卡 | 在后台阶(两线之间) | 这是正常区间;看 bo / wtps 的量级即可 |
| 调了比例没感觉 | 可能改的是互斥的另一组 | 用 sysctl vm.dirty_bytes 现读确认哪一组生效 |
| 上限算出来很小 | 可能用错了可用内存口径 | 改用 MemAvailable 重算 |
| 改不动、报权限或只读 | 权限面或文件系统面 | 核身份与挂载:只读就改成"只读算、交待办" |
改完怎么验证
- 上限算式含 ÷100,且用的是
MemAvailable(这两条是算对的前提); - 回读确认了"哪一组在生效"(互斥关系下,改错组等于没改);
- 写之前记了原值、并说清了持久化落点(
/etc/sysctl.d/+sysctl -p)。
这篇不解决什么
- 讲性能与掉电窗口的取舍的那篇管掉电窗口与性能之间的总取舍;本篇管"脏页两条水位线与回写节奏的读数"。判定入口:“掉电能丢多少、性能如何权衡”→ 那篇;“dirty 到了多少会开始回写/会把写入卡住”→ 本篇。
- 讲内存超额承诺的取舍的那篇管分配承诺;本篇管已分配内存的脏页回写节奏——一个在"分的时候"、一个在"写下去之后"。
- 讲内存口径打架时信谁的那篇管多个内存读数不一致时选哪个;本篇用它的结论(
MemAvailable)但只管脏页这一路。
踩坑清单
- 在只读环境里硬试写:权限不足与 Read-only file system 是两回事,前者是身份、后者是文件系统。
- 以为
sysctl -w会跨重启:它只改运行值,持久化要写/etc/sysctl.d/再sysctl -p。 - 把
centisecs当秒:单位在参数名里(百分之一秒),差 100 倍。 - 拿
vmstat首行当区间值:那是自启动以来的平均。 - 只看
Dirty不看节奏计数器:分不清"在回写"和"被卡住"。
- 点赞
- 收藏
- 关注作者
评论(0)