数据同步
juicefs sync
是强大的数据同步工具,可以在所有支持的存储之间并发同步或迁移数据,包括对象存储、JuiceFS、本地文件系统,你可以在这三者之间以任意方向和搭配进行数据同步。除此之外,还支持同步通过 SSH 访问远程目录、HDFS、WebDAV 等,同时提供增量同步、模式匹配(类似 rsync)、分布式同步等高级功能。
juicefs sync
功能的代码在社区版和企业版之间共享代码,因此即便交叉混用不同版本的 JuiceFS 客户端,sync
命令也能正常工作——除了一个特例,就是使用 jfs://
协议头的情况。社区版和企业版客户端有着不同的元数据引擎实现,因此如果用到了 jfs://
协议头,则不能混用不同版本的客户端。
juicefs sync
用法如下:
juicefs sync [command options] SRC DST
# 从 OSS 同步到 S3
juicefs sync oss://mybucket.oss-cn-shanghai.aliyuncs.com s3://mybucket.s3.us-east-2.amazonaws.com
# 从 S3 直接同步到 JuiceFS
juicefs sync s3://mybucket.s3.us-east-2.amazonaws.com/ jfs://VOL_NAME/
# 源端:a1/b1,a2/b2,aaa/b1 目标端:empty 同步结果:aaa/b1
juicefs sync --exclude='a?/b*' s3://mybucket.s3.us-east-2.amazonaws.com/ jfs://VOL_NAME/
# 源端:a1/b1,a2/b2,aaa/b1 目标端:empty 同步结果:a1/b1,aaa/b1
juicefs sync --include='a1/b1' --exclude='a[1-9]/b*' s3://mybucket.s3.us-east-2.amazonaws.com/ jfs://VOL_NAME/
# 源端:a1/b1,a2/b2,aaa/b1,b1,b2 目标端:empty 同步结果:a1/b1,b2
juicefs sync --include='a1/b1' --exclude='a*' --include='b2' --exclude='b?' s3://mybucket.s3.us-east-2.amazonaws.com/ jfs://VOL_NAME/
模式匹配
撰写同步命令的时候首先需要注意:SRC
和 DST
如果以 /
结尾将被视为目录(比如 movies/
)。没有以 /
结尾则会被视为「前缀」,将按照前缀匹配的规则进行匹配,例如,当前目录下有 test
和 text
两个目录,使用以下命令可以将它们同步到目标路径 ~/mnt/
:
juicefs sync ./te ~/mnt/
使用这种方式,sync
命令会以 te
前缀匹配当前路径下所有包含该前缀的目录或文件,即 test
和 text
,上方的命令会将他们拷贝到 ~/mnt/
这个目录下。
如果调整目标路径的前缀,例如将目标前缀改为 ab
,那么拷贝文件还会伴随着重命名:
# 目标路径中同步来的 `test` 目录名会变成 `abst`,`text` 会变成 `abxt`。
juicefs sync ./te ~/mnt/ab
类似 rsync,你可以用 --exclude
、--include
来过滤需要同步的文件,并通过多个规则的组合实现任意集合的同步,规则如下:
- 以
/
结尾的模式会仅匹配目录,否则会匹配文件、链接或设备; - 包含
*
、?
或[
字符时会以通配符模式匹配,否则按照常规字符串匹配; *
匹配任意非空路径组件,在/
处停止匹配;?
匹配除/
外的任意字符;[
匹配一组字符集合,例如[a-z]
或[[:alpha:]]
;- 在通配符模式中,反斜杠可以用来转义通配符,但在没有通配符的情况下,会按字面意思匹配;
- 始终以模式作为前缀递归匹配;
- 在使用包含/排除规则时,位置在前的选项优先级更高。
--include
应该排在前面,如果先设置--exclude '*'
排除了所有文件,那么后面的--include 'xxx'
包含规则就不会生效。
例如,同步所有文件,但排除隐藏文件(以 .
为开头命名均被视为隐藏文件):
# 排除隐藏文件
juicefs sync --exclude '.*' /tmp/dir/ s3://ABCDEFG:[email protected]/
重复该选项匹配更多规则,例如,排除所有隐藏文件、pic/
目录 和 4.png
:
juicefs sync --exclude '.*' --exclude 'pic/' --exclude '4.png' /tmp/dir/ s3://ABCDEFG:[email protected]
使用 --include
选项设置要包含的目录或文件,例如,只同步 pic/
和 4.png
两个文件,其他文件都排除:
juicefs sync --include 'pic/' --include '4.png' --exclude '*' /tmp/dir/ s3://ABCDEFG:[email protected]
模式匹配还可以实现删除存储系统中特定文件。诀窍是在本地创建一个空目录,将其作为 SRC
。
示范如下,谨慎起见,所有示范均添加了 --dry --debug
选项,因此命令不会实际删除任何文件,而是打印执行计划。验证成功后,去掉这两个选项便能实际执行:
mkdir empty-dir
# 删除 mybucket 中所有对象,但保留后缀名为 .gz 的文件
juicefs sync ./empty-dir/ s3://xxx:[email protected]/ --delete-dst --exclude=*.gz --dry --debug
# 删除 mybucket 中所有后缀名为 .gz 的文件
juicefs sync ./empty-dir/ s3://xxx:[email protected]/ --delete-dst --include=*.gz --dry --debug
同步行为
无挂载点同步
在两个存储系统之间同步数据,如果其中一方是 JuiceFS,推荐直接使用 jfs://
协议头,而不是先挂载 JuiceFS,再访问本地目录。这样便能跳过挂载点,直接读取或写入数据(该过程依然需要读取客户端配置文件,可以使用 juicefs auth
命令提前做好认证、生成配置文件),在大规模场景下,绕过 FUSE 挂载点将能节约资源开销以及提升数据同步性能。当使用 jfs://
协议头时,可以传入 juicefs mount
的挂载参数来帮助提升传输性能,比如 --max-downloads
, --max-uploads
, --buffer-size
等。
增量同步与全量同步
juicefs sync
默认以增量同步方式工作,对于已存在的文件,仅在文件大小不一样时,才再次同步进行覆 盖。在此基础上,还可以指定 --update
,在源文件 mtime
更新时进行覆盖。如果你的场景对正确性有着极致要求,可以指定 --check-new
或 --check-all
,来对两边的文件进行字节流比对,确保数据一致。
如需全量同步,即不论目标路径上是否存在相同的文件都重新同步,可以使用 --force-update
。
目录结构与文件权限
默认情况下,空目录不会被同步。如需同步空目录,可以使用 --dirs
选项。
另外,在本地、SFTP、HDFS 等文件系统之间同步时,如需保持文件权限,可以使用 --perms
选项。
符号链接
在本地目录之间同步时,支持通过设置 --links
选项开启遇到符号链时同步其自身而不是其指向的对象的功能。同步后的符号链接指向的路径为源符号链接中存储的原始路径,无论该路径在同步前后是否可达都不会被转换,注意:
- 符号链接自身的
mtime
不会被拷贝; --check-new
和--perms
选项的行为在遇到符号链接时会被忽略。
数据同步与碎片合并
对于顺序写场景,一定要尽力保证每个文件的写入都有最少 4M(默认块大小)的缓冲区可用,如果写并发太高,或者缓冲区设置太小,都会导致原本高效的“大块写”退化为“碎片化缓慢写”。叠加上 JuiceFS 的碎片合并,可能会带来严重的写放大问题。
碎片合并流量可以通过 juicefs_compact_bytes
这个指标来观测,JuiceFS Web 控制台的监控页面,以及私有部署环境的 Grafana 的对象存储流量板块,均将该指标画图呈现。如果在 sync
期间碎片合并流量很高,说明需要进行相关调优。推荐实践和调优思路如下:
- 如果对象存储的写带宽不足,慎用高并发(
--threads
),最好从默认值甚至更低的并发开始测起,谨慎增加到满意的速度; - 当目的地是 JuiceFS 文件系统的时候,推荐使用
jfs://
协议头,这种方式不需要 FUSE 挂载点,能减少资源开销,并且还提前针对碎片合并导致的写放大问题进行了优化(持久化间隔已经设置为每 60 秒一次,阅读下一点详细了解原理); - 如果必须使用 FUSE 挂载点来同步数据,那么对于大文件同步场景,建议调整挂载参数
--flush-wait=60s
,将默认 5 秒一次的持久化改为 60 秒,减少碎片量导致的写放大。详细阅读写放大的排查以了解更多; - 如果目的地是 JuiceFS 文件系统,确保该文件系统的 JuiceFS 客户端有着充足的读写缓冲区,按照每个文件的写入都必 须起码预留 4M 的写入空间,那么
--buffer-size
起码要大于等于--threads
参数的 4 倍,如果希望进一步提高写入并发,那么建议使用 8 或 12 倍的并发量来设置缓冲区。特别注意,根据写入目的地使用的协议头不同,设置缓冲区的方法也不同:- 目的地是
jfs://
协议头的文件系统,客户端进程就是juicefs sync
命令本身,此时--buffer-size
参数需要追加到juicefs sync
命令里; - 目的地是本地的 FUSE 挂载点,那么客户端进程是宿主机上运行的
juicefs mount
命令,此时--buffer-size
参数追加到该挂载点的juicefs mount
命令里。
- 目的地是
- 如果需要施加限速,那么加上了
--bwlimit
参数后,需要降低--threads
,避免过高的并发争抢带宽,产生类似的碎片化问题。每个对象存储的延迟和吞吐不尽相同,再次无法给出细致的调优计算流程,建议从更低的并发开始重新测试。
加速同步
juicefs sync
默认启用 10 个线程执行同步任务,可以根据需要设置 --threads
选项调大或减少线程数。但也要注意,受限于有限的单机资源,一味增加 --threads
未必能持续提升同步速度,反而可能会导致 OOM。因此如果同步速度不足,还需要考虑:
SRC
和DST
的存储系统是否已经达到了带宽上限,如果其中一个存储已经到达带宽限制,同步的瓶颈就在这里,增加并发度也不会继续提升同步速度;- 单机资源是否吃紧,比如 CPU、网卡拥堵。如果 同步受限于单机资源,那么可以考虑:
- 如果运行环境有硬件条件更好的节点(CPU、网络出口带宽等),可以换用该节点来运行
juicefs sync
,通过 SSH 访问源数据,例如juicefs sync root@src:/data /jfs/data
; - 使用分布式同步,在下方相关小节介绍。
- 如果运行环境有硬件条件更好的节点(CPU、网络出口带宽等),可以换用该节点来运行
- 如果同步的数据以小文件为主,并且
SRC
的存储系统的list
API 性能极佳,那么juicefs sync
默认的单线程list
可能会成为瓶颈。此时考虑启用并发list
操作,在下一小节介绍。
并发 list
在 juicefs sync
命令的输出中,关注 Pending objects
的数量,如果该值持续为 0,说明消费速度大于生产,可以增大 --list-threads
来启用并发 list
,以及用 --list-depth
来控制并发 list
的目录深度。
比方说,如果你面对的是 JuiceFS 所使用的对象存储服务,那么目录结构为 /<vol-name>/chunks/xxx/xxx/...
,对于这样的目录结构,使用 --list-depth=2
来实现对于 /<vol-name>/chunks
的并发列表操作,是比较合适的选择。
分布式同步
在两个对象存储之间同步数据,就是从一端拉取数据再推送到另一端,同步的效率取决于客户端与云之间的带 宽:
在同步大量数据时,单机带宽往往会被占满出现瓶颈,针对这种情况,考虑使用多机并发同步:
Manager 作为主控执行 sync
命令,通过 --worker
参数定义多个 Worker 节点(Manager 自身也参与同步),JuiceFS 会根据 Worker 的总数量,动态拆分同步任务并分发给各个节点并发执行,单位时间内能处理的数据量更大,总带宽也成倍增加。
在配置多机并发同步任务时,需要提前配置好 Manager 节点到 Worker 节点的 SSH 免密登录,如果 Worker 节点的 SSH 端口不是默认的 22,请在 Manager 节点的 ~/.ssh/config
设置其端口号。Manager 会将 JuiceFS 客户端程序分发到 Worker 节点,为避免兼容性问题,Manager 和 Worker 应使用相同类型和架构的操作系统。
举例说明,用分布式同步的方式进行对象存储间的数据同步:
juicefs sync --worker [email protected],[email protected] s3://ABCDEFG:[email protected] oss://ABCDEFG:[email protected]
运行这个命令后,当前节点与两个 Worker 节点 [email protected]
和 [email protected]
将共同分担数据同步任务。
上方的示范中是对象存储 → 对象存储的数据同步,如果需要基于 FUSE 挂载点做数据同步,那么可以在所有节点挂载 JuiceFS,然后用类似下方的命令来进行分布式同步:
# 在所有节点挂载 JuiceFS,以 parallel-ssh 为例,需要将配置文件复制到所有节点,然后挂载
parallel-scp -h hosts.txt -r /root/.juicefs /root/.juicefs
# 源文件系统需要更好的读性能,因此增大 buffer-size
parallel-ssh -h hosts.txt -i juicefs mount jfs-src /jfs-src --buffer-size=1024 --cache-size=0
# 目标文件系统需要更好的写性能
parallel-ssh -h hosts.txt -i juicefs mount jfs-dst /jfs-dst --buffer-size=1024 --cache-size=0 --flush-wait=60 --max-uploads=50
# 挂载完毕后,用下方命令拷贝数据
juicefs sync --worker host1,host2 /jfs-src /jfs-dst
观测和监控
简单来说,用 sync
命令拷贝大文件时,进度条可能会迟迟不更新,如果担心命令未能正常工作,可以用其他手段对传输情况进行观测。
sync
假定了使用场景是拷贝大量文件,因此进度的计算也是针对多个文件设计的:每一个文件完成了拷贝后,进度会更新一次。因此如果面对的都是大文件,单个文件的拷贝速度太慢,进度条就会变化缓慢,或者呈现卡死的状态。如果目的地端协议不支持 multipart upload(比如 file
、sftp
、jfs
协议头),单个文件会单线程进行复制,无法对大文件进行并发上传。可想而知,大文件 + 不支持 multipart upload,将会更容易出现进度条卡死的情况。
如果你观察到进度不再变化,参考下列手段进行观测和排查:
-
为
juicefs sync
添加--verbose
或--debug
参数,打印 debug 日志。 -
如果数据同步的两方有任何一个是 JuiceFS 宿主机挂载点:
- 用
juicefs stats
快速查看文件系统是否正在写入或读出 - 阅读客户端日志(默认
/var/log/juicefs.log
),观察是否有慢请求或者超时错误日志
- 用
-
如果数据同步的目的地是宿主机本地盘,可以直接观察目录下是否存在名称中带
.tmp.xxx
后缀的临时文件,sync
过程中会将传输结果写入临时文件,待传输完成后进行重命名,才完成最终的写入。观察临 时文件大小是否变化,就能确定当前的写入状况。 -
如果传输目的地均为对象存储,可以通过类似
nethogs
的命令,查看出入网流量,来判断传输进展。 -
以上手段均未能获得有效排查信息,则需要对
sync
进程采集 goroutine,发给 Juicedata 工程师进行排查:# 将 <PID> 替换为卡死的 sync 命令的 PID,记下 pprof 监听端口
lsof -p <PID> | grep TCP | grep LISTEN
# pprof 端口一般是 6061,如果已经被占 用,则需要递增,需要根据实际情况修改
curl -s localhost:6061/debug/pprof/goroutine?debug=1
如果需要监控 sync
命令的进度,可以使用 --metrics
参数指定监控指标地址,默认为 127.0.0.1:9567
。用 Prometheus 抓取这些指标,就能进行监控。
使用 S3 网关进行跨区域数据同步
通过 POSIX 方式访问 JuiceFS 时,会有频繁的元数据访问,跨区域访问元数据的延迟比较高会影响访问性能。如果需要跨区域传输大量小文件,这时元数据服务延迟高对性能影响更严重。
在这种情况下,可以通过跨区访问部署在源区域的 S3 网关来提升性能,它可以大幅减少跨区域访问的请求数。
阅读「S3 网关」学习如何使用和部署 S3 网关。