云服务器数据迁移的极致调度:利用cgroup blkio与tc实现业务无损的并行rsync增量同步

痛点:数据迁移不是简单的cp -a

当你从一台云服务器向另一台迁移数百GB甚至数TB的业务数据时,最大的敌人不是带宽,而是磁盘IO与网络对业务进程的干扰。传统的rsync -avz一把梭会导致同步进程吃满磁盘IO,数据库查询延迟飙升;或者网络打满引发ssh超时,迁移中途崩溃。更悲剧的是,事后发现大量文件校验不一致。

本文不讲空泛的迁移方案对比,直接剖析块级IO限流 + 网络整形 + 增量校验三合一的底层调度原理,并给出可复现的脚本套路。

原理:rsync增量同步的“三步哈希”与IO放大

滚动校验和算法

rsync经典的增量传输基于弱校验和(Adler-32)强校验和(MD5)的双重哈希。原理简述:

  • 接收方将旧文件切成固定大小(默认700字节)的块,计算每块的弱/强校验和,发送给发送方。
  • 发送方用滚动弱校验和扫描新文件,找到匹配块;必要时再用强校验和确认。
  • 仅传输不匹配的块(deltas)以及块匹配偏移信息。

这张算法的代价是:接收方需要逐块读取旧文件来构建校验列表,这会产生大量随机IO;发送方也需要扫描新文件并与校验列表比对。对于机械盘或IOPS受限的云盘,这一步往往比实际传输数据更慢。

IO放大效应

假设目标文件4KB,分块大小700字节,rsync需要读取6次才能完成校验构建。而实际业务中,一个PHP应用的日志目录可能有几十万个小文件,rsync的预扫描阶段会直接把磁盘IOPS打满。此时若业务数据库也在同一块盘上,延迟飙升是必然的。

解决方案:三层次限速调度架构

我们目标是:在保证业务QoS的前提下,最大化迁移吞吐。核心思路是让rsync进程的IO与网络请求“让路”。

第一层:cgroup blkio - 磁盘带宽硬上限

使用cgroup v1的blkio控制器,可以针对块设备限制特定进程的读写带宽。示例:

# 创建控制组
mkdir -p /sys/fs/cgroup/blkio/rsync_limit

# 设置写带宽限制(例:块设备vda,写入限制50MB/s)
echo "254:0 52428800" > /sys/fs/cgroup/blkio/rsync_limit/blkio.throttle.write_bps_device

# 设置读带宽限制(必须设置,因为rsync校验阶段以读为主)
echo "254:0 104857600" > /sys/fs/cgroup/blkio/rsync_limit/blkio.throttle.read_bps_device

# 启动rsync进程加入控制组
cgexec -g blkio:rsync_limit rsync -av --delete /data/ user@remote:/data/

注意:254:0是vda的主次设备号,可通过lsblk查看。建议读带宽设为写带宽的2倍,因为rsync校验阶段读密集,而传输阶段写密集(接收方写文件)。

第二层:tc网络整形 - 精确限制迁移流量

cgroup无法限制网络带宽,必须在网卡侧使用tc做排队整形。我们只对rsync的SSH连接(通常端口22)进行限速,避免其他业务流量被误伤。

# 添加htb根队列
tc qdisc add dev eth0 root handle 1: htb default 30

# 创建主类,限制总出口带宽200Mbps
tc class add dev eth0 parent 1: classid 1:1 htb rate 200mbit ceil 200mbit

# 创建子类专门用于ssh端口(22),限制80Mbps
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 80mbit ceil 80mbit

# 使用u32分类器匹配ssh流量
tc filter add dev eth0 protocol ip prio 1 u32 match ip sport 22 0xffff flowid 1:10
tc filter add dev eth0 protocol ip prio 1 u32 match ip dport 22 0xffff flowid 1:10

这样rsync的SSH流量被限制在80Mbps,而其他服务(如web、数据库)仍可使用剩余的120Mbps。注意服务器内网出口方向,如果rsync从本机推数据,限的是出方向(sport=22);若对方拉数据,则限入方向(dport=22),需要入向限速则在eth0的ingress上做(较为复杂,建议直接限出端)。

第三层:rsync自身软限速与并行度

cgroup和tc是硬限制,但rsync自带的--bwlimit可以作为软上限,防止突发流量打满tc预留的带宽。同时启用多线程并行传输,用--rsync-path配合管道或直接使用rsync -v --progress --partial --append-verify --bwlimit=8192(单位KB/s,80Mbps≈10MB/s,设为8192即可)。

# 并行传输多个目录(每个进程独立)
for dir in data1 data2 data3; do
    cgexec -g blkio:rsync_limit rsync -av --delete --bwlimit=8192 \
        /${dir}/ user@remote:/${dir}/ &
done
wait

注意:并行度以不超过vCPU核数为宜,IO密集型任务通常2-4个并发即可。同时使用ionice -c 2 -n 7降低进程的IO优先级(但cgroup blkio的优先级更有效,在这里可以省略ionice)。

数据校验:避开rsync --checksum陷阱

很多人为了数据一致性加上--checksum,但它会使rsync强制使用MD5全文件比对(不做增量),IO和CPU开销暴增。实战中,我们只在第一轮全量时用--checksum,后续增量只靠--append-verify或干脆取消校验。如果需要更强的端到端校验,可用独立的并行校验器:

# 在源端生成所有文件的BLAKE3哈希,传输后对比
find /data -type f -print0 | xargs -0 -P $(nproc) b3sum > /tmp/checksum.src
rsync -av /data/ user@remote:/data/
# 远程执行校验对比
ssh user@remote "find /data -type f -print0 | xargs -0 -P $(nproc) b3sum" | diff - /tmp/checksum.src

这种方案将校验与传输解耦,不会拖慢迁移速度。注意:需要远程也安装b3sum。

实战效果与调整要点

在一次对轻云互联某4C8G云服务器进行跨可用区迁移时(源盘为SSD云盘,目标盘为同一存储集群),我们使用了如下组合:

  • cgroup blkio: 读限制100MB/s,写限制50MB/s
  • tc: SSH端口出向限80Mbps
  • rsync: --bwlimit=8192 --delete --partial --append-verify
  • 并行2个目录同时同步

全量数据(200GB日志+MySQL物理文件)迁移耗时约45分钟,期间业务数据库保持在5000TPS,95%延迟未出现抖动。若不加任何限制,同样数据迁移会导致IO延迟瞬间飙到500ms以上,数据库直接报慢查询。

关键调整:读带宽限制要足够高,否则rsync扫描阶段会因IO欠配导致同步几乎停滞。通过iostat -x 1观察await%util,手动微调cgroup的bps值,找到业务容忍的临界点。

总结

云服务器数据迁移不是简单的移动文件,而是资源调度博弈。通过cgroup blkio锁定磁盘IO上限,tc分层保障网络带宽,再配合rsync的增量机制和独立校验,可以做到业务无感完成迁移。这套方法同样适用于后期定期增量同步,只需调整cgroup限制数值即可。记住:限制越精细,业务越稳定