rclone union 后端深度解析:策略驱动的多远程统一视图与 writeback 缓存
【免费下载链接】rclone"rsync for cloud storage" - Google Drive, S3, Dropbox, Backblaze B2, One Drive, Swift, Hubic, Wasabi, Google Cloud Storage, Azure Blob, Azure Files, Yandex Files项目地址: https://gitcode.com/GitHub_Trending/rc/rclone
union是 rclone 提供的"远程统一"后端(自 v1.44 引入),它把多个远程(远程或本地路径)合并成一个统一视图,让你用一条rclone命令同时操作多个存储端。本文基于官方文档 docs/content/union.md 并对照 backend/union/union.go、backend/union/policy/policy.go 等源码实现,完整覆盖 union 的配置流程、三大行为类别(action/create/search)下的 15 种策略、:ro/:nc/:writeback标签语义以及 writeback 缓存机制,读完后你可以独立完成 union remote 的配置、按配额/路径保留需求选择合适的策略组合,并理解其并行读写与配额缓存的底层实现。
union 是什么:把多个远程合并为一个入口
union后端将若干远程拼接在一起,形成一个统一的视图。在rclone config初始化配置时,你需要以空格分隔的列表指定上游(upstream)远程,上游既可以是本地路径,也可以是其他远程。
三个关键标签可以附加在 upstream 路径末尾:
:ro(read only):该远程只读,文件只从这里读取,绝不被写入;:nc(no create):该远程不允许创建新文件或新目录;:writeback:在其他远程中发现的文件会被回写到该远程,详见后文 writeback 小节。
例如remote:directory/subdirectory:ro或remote:directory/subdirectory:nc。
这些标签的解析实现在 backend/union/upstream/upstream.go:upstream.New按后缀顺序剥离:ro、:nc、:writeback并设置writable/creatable/writeback标志。其中:ro会同时置writable=false与creatable=false,:nc只置creatable=false——这与文档描述一致:read-only 远程既不能写也不能建,no-create 远程可以写已有文件但不能建新对象。
子目录映射与路径透传
upstream 中允许使用子文件夹。假设名为backup的 union remote 的上游是mydrive:private/backup,那么执行rclone mkdir backup:desktop与执行rclone mkdir mydrive:private/backup/desktop完全等价。
union 不会对含..段的路径做特殊处理:rclone mkdir backup:../desktop与rclone mkdir mydrive:private/backup/../desktop完全等价,路径段会原样透传到上游远程。
配置 union remote:完整交互流程
下面以把本地文件夹合并为一个名为remote的 union 为例。首先运行:
rclone config随后进入交互式配置流程:
No remotes found, make a new one? n) New remote s) Set configuration password q) Quit config n/s/q> n name> remote Type of storage to configure. Choose a number from below, or type in your own value [snip] XX / Union merges the contents of several remotes \ "union" [snip] Storage> union List of space separated upstreams. Can be 'upstreama:test/dir upstreamb:', '\"upstreama:test/space:ro dir\" upstreamb:', etc. Enter a string value. Press Enter for the default (""). upstreams> remote1:dir1 remote2:dir2 remote3:dir3 Policy to choose upstream on ACTION class. Enter a string value. Press Enter for the default ("epall"). action_policy> Policy to choose upstream on CREATE class. Enter a string value. Press Enter for the default ("epmfs"). create_policy> Policy to choose upstream on SEARCH class. Enter a string value. Press Enter for the default ("ff"). search_policy> Cache time of usage and free space (in seconds). This option is only useful when a path preserving policy is used. Enter a signed integer. Press Enter for the default ("120"). cache_time> Remote config Configuration complete. Options: - type: union - upstreams: remote1:dir1 remote2:dir2 remote3:dir3 Keep this "remote" remote? y) Yes this is OK e) Edit this remote d) Delete this remote y/e/d> y Current remotes: Name Type ==== ==== remote union e) Edit existing remote n) New remote d) Delete remote r) Rename remote c) Copy remote s) Set configuration password q) Quit config e/n/d/r/c/s/q> q三个策略项(action_policy默认epall、create_policy默认epmfs、search_policy默认ff)直接对应 backend/union/union.go 中fs.RegInfo声明的选项及其Default值,也对应 backend/union/common/options.go 的Options结构体(另含upstreams、cache_time、已弃用的remotes、min_free_space)。
基本用法:对 union remote 执行常规命令
配置完成后即可像操作普通远程一样使用:
列出remote1:dir1、remote2:dir2、remote3:dir3顶层目录:
rclone lsd remote:列出这三个远程中的全部文件:
rclone ls remote:把另一个本地目录复制进 union 中名为source的目录(按默认create_policy=epmfs,会被放到remote3:dir3,即路径存在且剩余空间最多的那个上游):
rclone copy C:\source remote:source从源码看,List的实现(backend/union/union.go)会对所有上游并发调用u.List(ctx, dir),再用mergeDirEntries按条目名(Remote())归并候选项——同名文件/目录来自不同上游时会被包装为携带全部候选的 unionObject/Directory,之后再由 search 策略从中挑选一个作为实际返回值;只有当所有上游都报"目录不存在"时才返回ErrorDirNotFound。这解释了为什么ls remote:能看到所有上游的合并视图。
行为类别与策略:action、create、search
union 后端的行为设计借鉴了 mergerfs 的思路:所有功能被划分为三个类别——action(修改已存在的文件/目录)、create(创建不存在的文件/目录)、search(读取与列举)。每个功能或类别都可以被赋予一个策略(policy),策略决定了执行该行为时选择哪个文件/目录(即选择哪个上游)。任意策略都可以分配给任意类别,尽管某些组合在实践中并不实用。例如rand(随机)用于文件创建尚可,但如果用于delete且同一文件存在多份副本时,行为会非常怪异。
功能类别划分
| 类别 | 说明 | 包含功能 |
|---|---|---|
| action | 写入已存在的文件 | move、rmdir、rmdirs、delete、purge,以及 copy、sync(作为目标且文件已存在时) |
| create | 创建不存在的文件 | copy、sync(作为目标且文件不存在时) |
| search | 读取与列举文件 | ls、lsd、lsl、cat、md5sum、sha1sum,以及 copy、sync(作为源) |
| N/A | 不适用 | size、about |
路径保留(Path Preservation)
策略分为两种基本类型:path preserving(路径保留)与non-path preserving(非路径保留)。
所有以ep开头的策略(epff、eplfs、eplus、epmfs、eprand、epall)都是路径保留型,ep即 existing path。路径保留策略只考虑"被访问的相对路径已经存在"的上游;使用非路径保留策略时,路径会在目标上游中按需创建(对应源码中mkdir会递归创建父目录,见 backend/union/union.go)。
依赖配额信息的策略
部分策略依赖配额(usage)信息。只有当上游支持相应配额字段时才应使用这些策略:
| 策略 | 所需字段 |
|---|---|
| lfs、eplfs | Free(剩余空间) |
| mfs、epmfs | Free(剩余空间) |
| lus、eplus | Used(已用空间) |
| lno、eplno | Objects(对象数量) |
要检查上游是否支持某字段,可运行rclone about remote: [flags]查看所需字段是否存在。源码侧对应 backend/union/upstream/upstream.go 中的About/GetFreeSpace/GetUsedSpace/GetNumObjects:这些值带有cache_time(默认 120 秒)的本地缓存,缓存过期后通过上游的About接口(15 秒超时)刷新;字段不支持时返回ErrUsageFieldNotSupported,策略会把该上游视为"无限空间"处理(见 backend/union/policy/epmfs.go 中的 Notice 日志)。
过滤规则(Filters)
策略的职责是搜索上游远程并为功能构造待操作的列表,负责过滤与排序。排序方式由策略类型决定,而过滤逻辑大体统一:
- 所有search策略不做过滤;
- 所有action策略会过滤掉标记为read-only(
:ro)的远程; - 所有create策略会过滤掉标记为read-only或no-create(
:nc)的远程。
若所有远程都被过滤掉,则返回错误。这与 backend/union/policy/policy.go 中的filterRO/filterNC一致:各策略的Action入口先调用filterRO,Create入口先调用filterNC,过滤后为空则返回fs.ErrorPermissionDenied。
全部策略说明
策略命名同样借鉴 mergerfs,但并不完全相同——由于远程文件系统的延迟远大于本地,部分策略语义有所调整。完整策略表如下:
| 策略 | 说明 |
|---|---|
| all | Search 类别:同epall。Action 类别:同epall。Create 类别:作用于所有上游。 |
| epall (existing path, all) | Search:按配置顺序,操作第一个相对路径存在者。Action:作用于所有找到的上游。Create:作用于所有相对路径存在的上游。 |
| epff (existing path, first found) | 按上游响应时间,操作第一个相对路径存在者。 |
| eplfs (existing path, least free space) | 在相对路径存在的所有上游中,选剩余空间最少者。 |
| eplus (existing path, least used space) | 在相对路径存在的所有上游中,选已用空间最少者。 |
| eplno (existing path, least number of objects) | 在相对路径存在的所有上游中,选对象数量最少者。 |
| epmfs (existing path, most free space) | 在相对路径存在的所有上游中,选剩余空间最多者。 |
| eprand (existing path, random) | 调用epall后随机化,只返回一个上游。 |
| ff (first found) | Search:同epff。Action:同epff。Create:按上游响应时间操作第一个找到的上游。 |
| lfs (least free space) | Search:同eplfs。Action:同eplfs。Create:选可用剩余空间最少的上游。 |
| lus (least used space) | Search:同eplus。Action:同eplus。Create:选已用空间最少的上游。 |
| lno (least number of objects) | Search:同eplno。Action:同eplno。Create:选对象数量最少的上游。 |
| mfs (most free space) | Search:同epmfs。Action:同epmfs。Create:选可用剩余空间最多的上游。 |
| newest | 选 mtime 最大(最新)的文件/目录。 |
| rand (random) | 调用all后随机化,只返回一个上游。 |
每个策略都是 backend/union/policy/ 目录下的独立文件(epall.go、epff.go、eplfs.go、epmfs.go、rand.go、newest.go等共 16 个),通过init()中的registerPolicy注册进全局策略表,policy.Get按名称(不区分大小写)取出,见 backend/union/policy/policy.go。
writeback:用:writeback搭一个简易读缓存
upstream 上的:writeback标签可以搭出一个简单的缓存系统:
[union] type = union action_policy = all create_policy = all search_policy = ff upstreams = /local:writeback remote:dir工作机制:
- 读路径:文件被打开读取时,如果文件在
remote:dir而不在/local,rclone 会先把文件完整复制到/local,再返回指向/local中副本的句柄。复制等价于rclone copy,因此若配置了--multi-thread-streams会被使用;所有回拷都会以 INFO 级别记录日志。 - 写路径:写入文件时会同时写入
remote:dir和/local两端。
upstreams中可以加入任意数量的远程,但:writeback标签只能有一个。rclone 除回写文件外不会以任何方式管理:writeback远程——过期文件清理、缓存容量控制等都需要自行处理。
源码印证了两处关键行为:
- 唯一性约束在 backend/union/upstream/upstream.go 的
Prepare中强制:发现 0 个 writeback 直接放行,超过 1 个则报错can only have 1 :writeback not %d,并为其余上游设置writebackFs指针; - 回读逻辑在 backend/union/entry.go 的
Object.Open:加锁后调用o.Object.Writeback(ctx),后者(backend/union/upstream/upstream.go)在存在writebackFs时通过operations.Copy把当前对象复制到 writeback 上游并返回新对象,随后Open打开的就是这个本地副本。
标准选项与高级选项
以下是 union 后端专属选项(摘自 docs/content/union.md 的自动生成选项段):
--union-upstreams
空格分隔的上游列表。可以写成'upstreama:test/dir upstreamb:'、'"upstreama:test/space:ro dir" upstreamb:'等形式(含空格的路径需加引号)。
- Config:
upstreams - Env Var:
RCLONE_UNION_UPSTREAMS - Type: string
- Required: true
--union-action-policy
ACTION 类别选择上游的策略。
- Config:
action_policy - Env Var:
RCLONE_UNION_ACTION_POLICY - Type: string
- Default:
epall
--union-create-policy
CREATE 类别选择上游的策略。
- Config:
create_policy - Env Var:
RCLONE_UNION_CREATE_POLICY - Type: string
- Default:
epmfs
--union-search-policy
SEARCH 类别选择上游的策略。
- Config:
search_policy - Env Var:
RCLONE_UNION_SEARCH_POLICY - Type: string
- Default:
ff
--union-cache-time
已用与剩余空间的缓存时间(秒)。仅在使用路径保留(ep*)策略时有意义。
- Config:
cache_time - Env Var:
RCLONE_UNION_CACHE_TIME - Type: int
- Default: 120
高级选项 --union-min-free-space
lfs/eplfs 策略可用的最低剩余空间。某远程剩余空间低于该值时,lfs/eplfs 策略不会考虑它。
- Config:
min_free_space - Env Var:
RCLONE_UNION_MIN_FREE_SPACE - Type: SizeSuffix
- Default: 1Gi
默认值 1Gi 与 backend/union/union.go 中Default: fs.Gibi一致。
--union-description
远程的描述。
- Config:
description - Env Var:
RCLONE_UNION_DESCRIPTION - Type: string
- Required: false
Metadata
底层远程支持的任意元数据都可读写(backend/union/union.go 中MetadataInfo亦如此声明;特性列表中ReadMetadata/WriteMetadata/UserMetadata均为开启)。
源码视角:union 的关键实现细节
配置校验与特性屏蔽
NewFs(backend/union/union.go)在构造阶段做了几条硬校验,排错时可直接对照报错:
upstreams为空 →union can't point to an empty upstream;- 只配置了 1 个上游 →
union can't point to a single upstream(union 至少需要 2 个上游); - 上游指向自己(
strings.HasPrefix(u, name+":"))→can't point union remote at itself。
此外保留了旧配置兼容:若旧版remotes设置存在而upstreams为空,除最后一个外的所有远程自动追加:ro后再作为upstreams使用。
union 的功能特性由所有上游"取交集"得出:features.Mask(ctx, f)对每个上游逐一屏蔽,只有全部上游都支持的能力(如 server-side move)才会暴露给 union;Hashes()返回各上游哈希集合的交集(Overlap,backend/union/union.go),意味着只要有一个上游不支持某哈希算法,union 层面就不报告该算法可用。
多上游并行写入:multiReader 扇出
当 create 策略(如all)选中多个上游时,put需要把同一份数据同时写入多个远程。backend/union/union.go 中的multiReader用io.Pipe+bufio.Writer把输入流"分叉"成 n 个只读流,io.MultiWriter汇聚写出,任何一条流的错误都会传播给其他流;随后put(backend/union/union.go)用 goroutine 池(multithread,backend/union/union.go)并行调用各上游的Put/PutStream,某一路失败时会排空其输入缓冲以不阻塞其他上传。同样的扇出/扇入模式也用于Object.Update(backend/union/entry.go):action_policy=epall更新一个在多个上游都存在的同名文件时,会并行更新所有候选。
合并列举与候选选择
List/ListR并发列举所有上游后按名字归并(mergeDirEntries,backend/union/union.go),特性CaseInsensitive置为 true,归并键会转小写以兼容不区分大小写的上游;- union 目录的
ModTime取所有候选的最大值、Size取所有候选之和(backend/union/entry.go); About会把各上游的 usage 逐字段累加,任何字段有一个上游缺失则整体置为未知(backend/union/union.go);epff的"first found"并非按配置顺序串行探测,而是并发向所有上游发起findEntry,按响应先后取第一个命中的上游(backend/union/policy/epff.go)——这与"first found by the time upstreams reply"的文档措辞严格对应。
实践建议
- 只读归档合并:把冷数据远程标
:ro(或:nc),写入自然落到可写上游;action/create 策略默认值(epall/epmfs)即可满足多数场景。 - 写入均衡:需要把新文件按容量调度到空间最多的上游时,保持默认
create_policy=epmfs,并用rclone about <upstream>预先确认该上游支持 Free 字段。 - 同名冲突:多个上游存在同名文件时,读端由
search_policy决定选中者;想让"最新修改者胜出"可考虑newest策略。 - 缓存型用法:本地目录 + 云端的"离线缓存"场景直接用
:writeback配置;注意 rclone 不负责清理本地缓存目录,需要自行控制其容量与过期。 - 行为验证可在 backend/union/union_test.go 与 backend/union/union_internal_test.go 中找到针对策略选择与并发操作的测试用例,作为策略行为的参照。
【免费下载链接】rclone"rsync for cloud storage" - Google Drive, S3, Dropbox, Backblaze B2, One Drive, Swift, Hubic, Wasabi, Google Cloud Storage, Azure Blob, Azure Files, Yandex Files项目地址: https://gitcode.com/GitHub_Trending/rc/rclone
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考