1. 从一块总是写满的磁盘说起
如果你管过几台跑着数据库或者日志服务的机器,大概率遇到过这种场景:一块 2TB 的盘,实际存进去的业务数据也就三四百个 G,但df -h一看,使用率已经飙到 80% 以上了。删了一批旧文件,空间释放得又慢又少,最后只能加盘或者迁移。这种"空间去哪儿了"的问题,很多时候不是数据真的多,而是重复数据和稀疏文件在悄悄吃掉容量。
VDO,全称 Virtual Data Optimizer,就是冲着这个问题来的。它是 Linux 平台上的一套存储层技术,工作在块设备之上、文件系统之下,核心干两件事:去重和压缩。你可以把它理解成一个"存储空间的中间商"——上层文件系统以为自己在往一块大硬盘里写,实际上 VDO 在背后把重复的块合并、把能压的块压掉,最终落到物理盘上的数据量可能只有原来的几分之一。
这篇内容适合谁看?一类是手里有虚拟化平台、容器镜像仓库、备份存储的运维,这类场景重复数据多,VDO 收益最明显;另一类是刚接触 Linux 存储栈、想搞清楚 LVM、设备映射、文件系统之间还能插进什么层的人。我会从 VDO 到底怎么工作讲起,然后一步步带你把它搭起来、用起来,最后重点聊那些文档里不太写、但实际会踩的坑。全程基于常见的 RHEL 系发行版实践,命令可以直接抄。
需要先说明一点:VDO 不是"免费的午餐",它用 CPU 换空间,用一层抽象换容量。搞清楚它的边界,比会敲几条命令重要得多。
2. VDO 到底在存储栈的哪一层动手
2.1 去重和压缩是两件不同的事
很多人把 VDO 简单理解成"压缩",这不太准确。它内部其实是两条独立的流水线:
去重(Deduplication)针对的是"内容相同的块"。VDO 把落到它上面的数据切成固定大小的块(默认 4KB),对每个块算一个指纹(哈希),然后拿指纹去索引里查:这个块之前是不是存过?存过就直接引用已有的物理块,不再写一份。所以去重的前提是数据有重复——同一份镜像被复制了十次、同一批日志模板反复出现,这种场景去重率能到 10:1 甚至更高;反过来,如果存的全是加密后的随机数据,去重基本没收益。
压缩(Compression)针对的是"单个块内部能不能变小"。VDO 默认用 LZ4 算法,对每个块独立压缩。文本、日志、部分二进制数据压缩效果不错,已经压过的格式(比如 JPEG、zip)再压就没意义了。
这两条流水线的顺序是先去重、后压缩。逻辑也说得通:先看这个块是不是重复的,重复的直接省掉;不重复的再试着压一压。
2.2 它在设备映射里的位置
VDO 是基于device mapper实现的一个目标(target),叫dm-vdo。所以它在存储栈里的位置很清晰:
应用 / 文件系统(XFS、ext4) ↓ VDO 卷(一个块设备,比如 /dev/mapper/vdo0) ↓ dm-vdo 目标(去重 + 压缩) ↓ 底层物理设备(可以是分区、LVM 逻辑卷、甚至另一层 RAID)关键点在于:VDO 呈现给上层的是一个"逻辑块设备",这个设备的逻辑大小可以远大于底层物理设备。比如你给它一块 1TB 的物理盘,可以对外宣称这是一块 10TB 的 VDO 卷。这不是虚标,而是因为去重压缩后实际占用会小很多,VDO 用"逻辑大小"来容纳这些被优化过的数据。当然,如果实际数据真的涨到超过物理容量,就会出问题——这个后面单独讲。
2.3 为什么是"块级"而不是"文件级"
市面上也有文件级的去重方案,VDO 选择块级是有取舍的。块级的好处是对上层透明:不管上面跑的是 XFS、ext4 还是别的,VDO 都一视同仁,它根本不关心文件系统怎么组织数据,只认块。坏处是它看不到文件语义,没法做"这个文件删了所以相关块可以回收"这种判断,只能靠块本身的内容和引用计数。
理解这一点很重要,因为它直接解释了一个常见困惑:为什么删了文件,VDO 的空间没马上回来?因为 VDO 层面看到的是块被改写了,它需要等这些块不再被引用,才会真正释放物理空间。这个回收是有延迟的,后面会细说。
3. 动手之前:容量规划和参数怎么定
3.1 逻辑大小不是拍脑袋定的
创建 VDO 卷时有两个尺寸概念必须分清:
- 物理大小(physical size):底层实际给它的空间,比如 1TB。
- 逻辑大小(logical size):对外呈现的大小,比如 10TB。
逻辑大小和物理大小的比值,决定了 VDO 内部索引结构要预留多少元数据空间。VDO 需要维护一张"块指纹 → 物理位置"的映射表,这张表本身要占空间。逻辑大小越大,表越大。
经验上,逻辑大小不要超过物理大小的 10 到 20 倍。超过这个范围,元数据开销会变得很夸张,而且一旦实际数据量逼近物理容量,你会非常被动。我一般按预期去重压缩比来倒推:如果估计能压到 3:1,那 1TB 物理盘对外给 3TB 逻辑大小就够了,留点余量给突发增长。
3.2 索引模式的选择
VDO 的索引(index)负责存指纹映射,有两种模式:
| 模式 | 特点 | 适用场景 |
|---|---|---|
| 同步索引(sync) | 写数据时同步更新索引,一致性更强 | 对数据安全要求高、写入不密集 |
| 异步索引(async) | 索引更新异步进行,性能更好 | 写入量大、能接受一定风险 |
异步模式性能明显更好,但机器异常掉电时,最近的索引更新可能丢失,导致部分去重信息失效(数据本身不丢,只是去重率下降)。生产环境里如果对性能敏感,通常选异步,然后配合 UPS 或者定期同步来兜底。
3.3 块大小和压缩的取舍
VDO 默认块大小 4KB,这个值一般不用改。块太小,索引条目爆炸,元数据吃空间;块太大,去重粒度变粗,小文件场景收益下降。4KB 是个平衡点。
压缩默认开启,算法 LZ4。LZ4 的特点是快,压缩比一般,但对存储层来说延迟比压缩比更重要。如果你的数据是高度可压的文本,可以考虑更激进的算法,但大多数场景 LZ4 够用。
提示:规划阶段一定要先想清楚"这块盘上跑的是什么数据"。虚拟化镜像、容器层、备份归档——这三类去重收益最高;数据库数据文件、已加密数据、视频素材——收益很低甚至为负(白白消耗 CPU)。
4. 从裸盘到可用 VDO 卷的完整搭建
4.1 环境准备和软件安装
先确认内核支持 dm-vdo。较新的 RHEL 系发行版(8 及以上)默认带,老版本可能需要额外装模块。检查一下:
modinfo dm-vdo有输出说明模块在。然后装管理工具:
# RHEL/CentOS 系 dnf install -y vdo kmod-kvdo # 确认版本 vdo version装完加载模块:
modprobe dm-vdo lsmod | grep vdo4.2 创建 VDO 卷
假设底层用/dev/sdb这块 1TB 的盘。先别急着直接用它,建议在它上面建个 LVM 逻辑卷再给 VDO,这样以后扩容、迁移都灵活。不过为了讲清楚,这里先用裸盘演示,后面再说 LVM 的玩法。
# 基本创建:物理 1TB,逻辑 3TB,异步索引,开压缩 vdo create \ --name=vdo0 \ --device=/dev/sdb \ --vdoLogicalSize=3T \ --indexMem=0.5 \ --blockMapCacheSize=128M \ --writePolicy=async \ --compression=on几个参数解释一下:
--vdoLogicalSize=3T:对外逻辑大小 3TB。--indexMem=0.5:给索引分配 0.5GB 内存。这个值影响能缓存多少指纹映射,太小会导致频繁读盘查索引,性能掉得厉害。一般按每 TB 逻辑大小配 0.25 到 0.5GB 估。--blockMapCacheSize=128M:块映射缓存,也是性能相关。--writePolicy=async:异步写策略。
创建完看一下状态:
vdo status --name=vdo0重点看Deduplication、Compression是不是 enabled,以及Logical size、Physical size对不对。
4.3 格式化并挂载
VDO 卷创建好后,会出现一个设备/dev/mapper/vdo0。像对待普通块设备一样格式化:
mkfs.xfs /dev/mapper/vdo0 mkdir -p /data mount /dev/mapper/vdo0 /data写进/etc/fstab让它开机自动挂载:
/dev/mapper/vdo0 /data xfs defaults 0 0注意:VDO 卷的挂载要确保 VDO 服务先起来。用 systemd 管理的话,
vdo.service会处理依赖,但如果你手动写 fstab,最好加上x-systemd.requires=vdo.service之类的依赖,避免开机时设备还没就绪就挂载失败。
4.4 验证去重压缩是否真的生效
搭完别急着上业务,先做个验证。往/data里写一份有重复内容的数据,看 VDO 的统计:
# 写 1GB 的重复数据 dd if=/dev/zero of=/data/test1 bs=1M count=1024 cp /data/test1 /data/test2 cp /data/test1 /data/test3 # 看 VDO 统计 vdostats --human-readablevdostats会输出几个关键指标:
| 指标 | 含义 |
|---|---|
| 1K-blocks | 逻辑大小 |
| Used | 实际占用的物理空间 |
| Available | 剩余物理空间 |
| Used% | 物理使用率 |
| Saving% | 节省比例 |
| Write policy | 写策略 |
如果三份 1GB 的相同数据写进去,Used应该只涨了 1GB 左右,Saving%会很高。这就说明去重在工作。如果 Saving% 一直是 0,那要么数据本身没重复,要么配置有问题。
5. 那些文档不写、但一定会踩的坑
5.1 空间"删了不还"的真相
这是 VDO 被吐槽最多的一点。你删了一大批文件,df看文件系统层面空间是回来了,但vdostats里Used几乎没动。原因在于:VDO 是块级的,它不知道文件被删了。文件系统删除文件,只是把对应的块标记为"可复用",并没有通知 VDO 这些块可以回收。VDO 要等到这些块被新数据覆盖,才会重新计算指纹、释放旧的物理块。
那怎么主动回收?VDO 提供了fstrim支持。前提是文件系统支持 discard,并且挂载时开启了。XFS 默认支持,但要在挂载选项里加discard,或者定期手动跑:
# 手动触发 TRIM,让文件系统告诉 VDO 哪些块空了 fstrim -v /data跑完之后再看vdostats,Used会下降。但要注意,fstrim对 VDO 来说不是"立即释放",它只是把块标记为空,VDO 后台还需要时间处理。而且频繁 TRIM 对性能有影响,生产环境一般用定期任务(比如每周一次)而不是挂载时开discard。
提示:如果你的业务是"写多删少"型(比如日志归档),空间回收压力不大;如果是"反复覆盖"型(比如镜像仓库不断推新版本删旧版本),一定要配好 TRIM,否则物理盘会被慢慢撑满。
5.2 物理空间真的满了会怎样
前面说逻辑大小可以远大于物理大小,但这有个前提:实际数据量不能超过物理容量。一旦去重压缩后的数据把物理盘写满,VDO 会进入只读或者报错状态,上层文件系统跟着出问题。
这个坑的隐蔽性在于:df看逻辑卷还有大量空间,但底层已经满了。所以监控 VDO 一定要看vdostats的Used%,而不是只看df。我一般会设两级告警:物理使用率 70% 预警,85% 严重告警,留出处理时间。
真满了怎么办?最直接的是加物理盘扩容。VDO 支持在线扩容物理层:
# 假设底层是 LVM,先扩 LV lvextend -L +500G /dev/vg0/vdo_pool # 然后扩 VDO 物理层 vdo growPhysical --name=vdo05.3 内存和 CPU 的隐性成本
VDO 不是零成本的。去重要做哈希计算,压缩要跑 LZ4,这些都要 CPU。索引要常驻内存,逻辑卷越大、去重率越高,内存需求越大。一台跑 VDO 的机器,如果索引内存给少了,会频繁触发索引换入换出,性能断崖式下跌。
实测经验:每 TB 逻辑大小预留 0.5GB 到 1GB 内存比较稳妥。如果机器内存紧张,宁可把逻辑大小调小,也别把索引内存压太狠。CPU 方面,去重压缩是并行的,多核机器收益明显,单核老机器上 VDO 可能成为瓶颈。
5.4 和 LVM、RAID 的叠加顺序
VDO 可以和 LVM、RAID 组合,但顺序有讲究。推荐的结构是:
RAID(物理冗余) ↓ LVM 逻辑卷(灵活扩容) ↓ VDO(去重压缩) ↓ 文件系统也就是VDO 放在 LVM 之上、文件系统之下。这样 LVM 负责底层空间的灵活管理,VDO 专注优化。反过来把 VDO 放在 RAID 下面(先 VDO 再 RAID)通常没意义,因为 RAID 会复制数据,去重收益被抵消。
6. 性能调优和日常维护的几个抓手
6.1 索引内存和缓存怎么调
如果发现 VDO 性能不理想,先看索引命中情况。vdo status里有一些统计,但更直接的是观察系统 IO:如果 VDO 底层盘持续有大量随机读,很可能是索引缓存不够,在反复读盘查映射。
调整索引内存需要重建卷(因为索引结构在创建时确定),所以规划阶段就要给够。块映射缓存(block map cache)倒是可以运行时调:
vdo setBlockMapCacheSize --name=vdo0 --blockMapCacheSize=256M加大缓存能减少元数据读盘,但吃内存。一般 128M 到 256M 是常见区间。
6.2 什么时候该关掉去重或压缩
不是所有场景都适合全开。举两个反例:
- 存已加密数据:加密后数据块几乎无重复,去重白算哈希,纯浪费 CPU。这种场景可以只开压缩或者全关。
- 存视频、图片素材:本身已压缩,VDO 再压收益极低,去重也难命中。这种盘干脆别用 VDO,直接裸盘或 RAID。
关掉某个功能:
vdo disableDeduplication --name=vdo0 vdo disableCompression --name=vdo0反过来也能开。但注意,开关去重会影响已有数据的索引,切换后可能需要一段时间重新整理。
6.3 监控要看哪些指标
日常巡检 VDO,我固定看这几项:
| 指标 | 命令 | 关注点 |
|---|---|---|
| 物理使用率 | vdostats | 超过 85% 要处理 |
| 节省比例 | vdostats | 突然下降说明数据特征变了 |
| 写策略 | vdo status | 确认没被意外改回 sync |
| 索引状态 | vdo status | 看是否有异常 |
| 底层盘健康 | smartctl | VDO 不解决硬件故障 |
把这些接进监控系统,比出事之后再查强得多。
6.4 升级和迁移的注意事项
VDO 卷迁移到别的机器,不能简单dd底层盘就完事,因为索引和元数据是绑定的。正确做法是用vdo工具导出导入,或者干脆在新机器上重建、用文件级同步迁移数据。
升级内核或 VDO 版本前,先看发行版的 release notes,确认兼容性。升级后第一时间vdo status检查卷状态,别等业务报错才发现问题。
7. 我踩过的几个真实教训
说几个我自己在实际操作里栽过的跟头,比参数表更有参考价值。
第一个是逻辑大小给太贪。早期我给一块 2TB 的盘配了 20TB 逻辑大小,想着去重率高能撑住。结果业务数据比预期"不重复"得多,物理盘半年就快满了,而逻辑层还显示大量空闲。扩容时又发现索引内存当初按 20TB 配的,机器内存吃紧。后来学乖了,逻辑大小按保守估计给,宁可后面扩。
第二个是忘了配 TRIM。一个镜像仓库场景,天天推新镜像删旧镜像,物理盘使用率一路涨到 90% 多,df看逻辑卷才用了一半。查了半天才想起来没做空间回收。加上定期fstrim之后,使用率稳定在 60% 左右。
第三个是在错误的场景硬上 VDO。有次给一个存加密备份的盘配了 VDO,结果去重率接近 0,压缩也几乎没效果,白白多了一层开销,性能还降了。后来把 VDO 去掉,直接裸盘,反而更稳。这件事让我明白:VDO 是优化工具,不是万能药,用之前先判断数据特征。
最后一个偏细节:异步索引 + 非正常关机。有次机器断电,重启后 VDO 卷能起来,但去重率明显下降,因为最近的索引更新丢了。数据没丢,但空间效率受影响。后来给关键机器配了 UPS,并且定期做索引同步,才算稳下来。
8. 关于 VDO 适用边界的一点个人判断
用不用 VDO,我的判断标准其实很简单:看这块盘上的数据"重复度"和"可压性"高不高,以及你对 CPU、内存开销的容忍度。
虚拟化平台、容器镜像、开发测试环境、备份归档——这些场景重复数据天然多,VDO 收益明显,值得上。数据库主库、加密存储、多媒体素材库——这些场景收益低,硬上反而添乱。中间地带比如通用文件服务器,可以先小范围试,用vdostats观察实际节省比例,再决定要不要推广。
还有一点,VDO 解决的是"空间效率",不解决"数据安全"。它不做冗余,底层该 RAID 还得 RAID;它也不替代备份,去重后的数据一样会丢。把它放在存储栈里正确的位置,配合 LVM 做灵活管理、配合监控做容量预警,这套组合用起来才踏实。
如果你正准备在环境里引入 VDO,我的建议是先拿一块非关键盘做验证,把去重率、性能影响、空间回收这几件事摸清楚,再往生产推。存储这层的东西,改起来动静大,前期多花半天验证,能省后面很多麻烦。