根本原因是SSD以4KB page为最小读写单位,小写需读-改-写整页,导致IO放大;机械盘则受限于sector(512B或4KB)对齐要求。

为什么写1个字节,磁盘实际要写4KB?
根本原因在于硬件层的最小操作单元限制:SSD以page(通常4KB)为读写基本单位,机械盘则以sector(传统512B,现多为4K native)为准。当你发起一个未对齐的write()——比如偏移量是4097字节、长度1字节——底层无法只刷1字节,必须:
- 读出整个目标
page(4KB)到SSD控制器缓存; - 在内存中合并新数据;
- 擦除原
page(SSD不可覆写); - 把整页4KB写入新位置。
page跨两个物理erase block,还可能引发额外迁移,进一步放大。
文件系统和分区不对齐,会雪上加霜
即使应用层写请求对齐,如果底层布局没对齐,照样放大。典型场景:
- 分区起始扇区不是4096字节(8×512B)的整数倍 →
fdisk -l看Start列,若不是8的倍数,就是错的; - ext4格式化时没指定
-E stride=128,stripe-width=256等RAID感知参数,导致元数据分布打乱对齐; - LVM物理扩展
PE大小(默认4MB)与SSDpage或erase block不匹配,中间多一层映射损耗。
O_DIRECT写了4KB对齐buffer,内核下发到底层的bio却仍被拆成多个非对齐request。
iostat -x里哪些指标暴露了读写放大?
光看wkB/s和w/s不够,关键要看放大比:
- 计算平均写大小:
avgqu-sz / (w/s)或直接看avgrq-sz(单位扇区);若长期(即- 对比
r/s和w/s:SSD负载下r/s ≪ w/s但rkB/s ≈ wkB/s,大概率在后台GC或rewrite;- 观察
%util高但await异常飙升(如>50ms):说明请求在队列堆积,背后常是频繁的read-modify-write循环。 - 对比
iostat不显示底层擦除次数,需结合smartctl -a /dev/nvme0n1 | grep -i "media wear"看SSD磨损指标佐证。
避免放大的实操底线
不是所有场景都能根治,但守住这三条能拦住80%问题:
- 新建分区时强制4K对齐:
fdisk /dev/sda→g(GPT)→n→ 回车让起始扇区默认从2048开始(=1MB对齐); - SSD上禁用
barrier和journal开销(仅限数据盘):mkfs.ext4 -O ^has_journal /dev/sda1,挂载加noatime,nodiratime,discard; - 应用写文件前检查buffer地址和offset:
posix_memalign(&buf, 4096, size)+lseek(fd, offset & ~4095, SEEK_SET),再write()。
io_uring提交聚合或换用libpmem直写持久内存——普通块设备上,对齐只是起点,不是终点。










