使用Linux kernel dm-mirror实现设备间同步

本文同步自(最佳显示效果请点击):https://zohead.com/archives/dm-mirror-sync-device/

在某些场合下,用户需要在 Linux 系统中实现直接的块设备同步方式,需要将一个裸设备中的内容完全同步到另一个裸设备,有时甚至需要能自定义位移和大小进行同步,这时 Linux 自带的 dm-mirror 模块很可能是比较好的实现方案。

dm-mirror 是 Linux kernel 中 device-mapper(DM)模块(device-mapper 内部称为 target)中的一个,可以实现多个块设备间的数据同步,并支持通过 dm-log 模块保存同步时的元数据信息,方便在关机和重启之后能够继续之前的进度进行同步,而且 dm-mirror 的元数据可以选择保存在内存中或者固定的块设备中。LVM 逻辑卷管理中的 pvmove 命令可以在卷组中替换物理卷,pvmove 命令正是基于 dm-mirror 模块实现的。Linux kernel 中还包含了其它的一些 device-mapper target 模块,例如 dm-linear(用于实现 LVM 逻辑卷管理)、dm-crypt(加密数据)、dm-snapshot(用于实现即时快照)等。

dm-mirror 的主要实现代码在 Linux kernel 代码树的 drivers/md/dm-raid1.c 中,dm-mirror 的日志元数据记录功能通过 dm-log 模块实现,同步的区块划分由 dm-region-hash 模块实现,底层的同步实现则由 kcopyd 来实现。kcopyd 的功能就是从一个块设备拷贝一定扇区的数据到另一个或多个块设备,kcopyd 支持异步的完成通知功能,目前主要被 dm-snapshot 和 dm-mirror 模块使用。有关 Linux kernel 中 kcopyd 的说明请参考这里:http://www.kernel.org/doc/Documentation/device-mapper/kcopyd.txt

dm-mirror 设备的装载通过 dmsetup 命令来实现,以下说明的地址都是以扇区为单位(固定为 512 个字节),先看看 dm-mirror target 的 table 格式:

start length mirror log_type #logargs logarg1 ... logargN #devs device1 offset1 ... deviceN offsetN [#features <features>]

对每个值分别说明如下:

  • start 为虚拟设备起始扇区地址,一般固定为 0
  • length 为 dm-mirror 设备的大小,也就是同步的总扇区大小
  • mirror 值固定,表示这是一个 dm-mirror 设备
  • log_type 指定日志元数据类型,常用的是 core 和 disk 两种类型
  • #logargs 表示后面的 logarg1 ... logargN 一共有多少个
  • #devs 表示需要同步的设备个数,每个设备都有一个 device 值和一个 offset 值
  • deviceN offsetN 为每个需要同步的设备的设备路径(或者设备号)和偏移地址
  • #features 和 features 为可选的值,#features 表示可选项个数
  • 如果指定了 #features,则必须为 1,且 features 必须为 handle_errors(表示 dm-mirror 会自动进行错误处理,即同步出错时自动停止同步并删除 dm-mirror 虚拟设备)

然后是 core 和 disk 两种日志元数据类型的介绍(另外有 clustered_core 和 clustered_disk 两种适用于集群的日志类型,有兴趣可以自己去了解下哦):

  • core 类型(内存):
    日志保存在内存中,关机或者重启之后会丢失,因此安全性较差,但由于元数据存放在内存中,性能相对 disk 类型来说是非常好的。#logargs 个数为 1-2 个,日志参数格式为:
    regionsize [[no]sync]regionsize 为日志区块的大小(512 个字节的扇区为单位),可选的 sync 和 nosync 用于指定 dm-mirror 是否已经同步过。
  • disk 类型(磁盘):
    日志保存在磁盘(块设备)中,因此可以在关机或重启之后继续之前的同步进度,当然性能比 core 类型肯定是要差一些的。#logargs 个数为 2-3 个,日志参数格式为:
    logdevice regionsize [[no]sync]logdevice 指定日志元数据存放在哪个块设备上,可以使用设备路径或者主从设备号形式,其它参数与 core 类型相同。

Linux 系统中 dm-mirror 具体使用步骤举例如下:

  1. 首先加载 dm-mirror 模块:
    modprobe dm-mirror
  2.  准备好需要同步的设备,假设是实际应用中的一种常见需求:Linux 系统盘为 /dev/sda,系统中另外有个数据盘 /dev/sdb。但由于磁盘 /dev/sdb 寿命快到了,需要将 /dev/sdb 磁盘上的数据同步到 /dev/sdc 上,/dev/sdc 的容量比 /dev/sdb 要大。

    先得到 /dev/sdb 和 /dev/sdc 的容量大小(以 512 个字节的扇区为单位):
    blockdev --getsz /dev/sdb
    blockdev --getsz /dev/sdc

    假设分别为 209715200(100G) 和 314572800(150G)。
  3. 在系统中通过文件方式产生 loop 设备用作 dm-mirror 同步存放日志元数据的设备,防止关机或重启之后同步进度丢失,1MB 的文件已完全足够保存日志元数据:

    dd if=/dev/zero of=meta.dat bs=1024k count=1
    运行完成将产生一个 1MB 大小的 meta.dat 文件。
  4. 准备 loop 设备:

    losetup -f
    得到空闲的 loop 设备名,假设为 /dev/loop0

    losetup /dev/loop0 meta.dat
    装载 loop 设备。
  5. 运行 dmsetup create 命令创建 dm-mirror 设备进行同步:

    echo "0 209715200 mirror disk 2 /dev/loop0 16384 2 /dev/sdb 0 /dev/sdc 0 1 handle_errors" | dmsetup create mirror0

    运行成功之后将产生名称为 mirror0 的虚拟 dm-mirror 设备。

    echo 中间的字符串即为 dm-mirror 的 table 格式:209715200 为同步的大小,disk 指定元数据保存在磁盘中,/dev/loop0 为元数据设备,16384 为区块大小(这里指定为 8MB 为提高同步性能),后面的参数指定将 /dev/sdb(源设备,起始地址为 0)的数据同步到 /dev/sdc(目标设备,起始地址为 0),1 和 handle_errors 表示不忽略同步错误。
  6. 查询同步状态和进度:

    dmsetup status mirror0

    输出类似下面:

    mirror0: 0 209715200 mirror 2 8:16 8:32 417/12800 1 AA 3 disk 7:0 A

    8:16 和 8:32 为 /dev/sdb 和 /dev/sdc 的设备号,209715200 个扇区的数据以 16384 个扇区为单位可得到需要划分为 12800 个区块,现在已经同步了 417 个区块,AA 表示两个要同步的设备都正常,7:0 为日志元数据设备 /dev/loop0 的设备号。
  7. 定期运行 dmsetup status mirror0 查看进度,同步完成时,进步部分将显示为:12800/12800,表示所有区块都已经同步好了;
  8. 删除没有用了的 dm-mirror 设备:
    dmsetup remove mirror0
  9. 删除没有用了的 loop 设备:
    losetup -d /dev/loop0
  10. 至此两块磁盘间的同步操作已经完成,在此过程中间即使有非正常的重启或者关机操作,也只需要在重新启动之后,再运行第 4 和 第 5 步就可以继续重启之前的进度进行同步。

从上面的例子可以看出 dm-mirror 在 Linux 系统中还是很有用处的。实际上上面的例子中在同步完成之后就把 dm-mirror 设备删除了,如果不删除的话,是可以直接继续对 dm-mirror 设备(上面例子中的 dm-mirror 设备的实际路径为:/dev/mapper/mirror0)进行读写的,dm-mirror 设备在写时会自动将下面的设备数据进行同步,这样实际上就是类似 RAID1 磁盘阵列的效果了。

另外一种比较典型的应用是利用 dm-mirror 实现两个 SAN 网络设备间的同步,这看起来比较类似于 DRBD 的功能,但在实现上远比 DRBD 要简单和透明的多。可以实现单独的服务器分别连接两个不同的 SAN target(光纤、Infiniband 或者 iSCSI 都可以),得到两个不同的 SCSI 设备,然后就可以使用 dm-mirror 创建新的虚拟设备,在这个新的虚拟设备上的读写操作会在 Linux kernel 底层自动同步到对应的两个 SAN 设备中。

由于 dm-mirror 没有比较正式的官方文档,以上为个人参考 Linux kernel 源码和相关说明总结出来的,其中有任何问题欢迎提出指正,玩的开心 ^_^