Seaweed中filer.sync原理

文章目录

一、当前 filer.sync 的整体结构

假设启动:

bash 复制代码
weed filer.sync \
  -a=computer-a:8888 \
  -b=computer-b:8888

逻辑可以理解为:

text 复制代码
生产者
   |
   v
A Filer
   |
   | metadata subscription
   v
filer.sync 进程
   |
   | 读取 A 的文件数据
   | 写入 B 的数据和 metadata
   v
B Filer

filer.sync 可以运行在 A、B 任意一台机器,或者第三台机器上。A 和 B 本身不直接建立"文件同步通知"关系,实际中转者是 filer.sync 进程。

如果没有设置 active-passive 模式,程序会同时启动两个方向:

text 复制代码
A -> B
B -> A

二、一次文件创建的完整流程

1. 客户端写入 A

生产者向 A 写入文件时,通常会经历:

  1. 文件数据写入 A 的 volume server,或者通过 A filer 代理写入。
  2. A filer 创建或更新 metadata entry。
  3. A filer 将 metadata 变化写入 metadata log。
  4. A filer 通过 SubscribeMetadata 将变化发送给订阅者。

这里的"通知"是 A filer 通知 filer.sync,不是 A filer 直接调用 B filer。

对应代码入口主要在:

  • weed/command/filer_sync.go
  • weed/pb/filer_pb_tail.go
  • weed/server/filer_grpc_server_sub_meta.go

2. filer.sync 从 A 接收事件

filer.sync 会通过:

go 复制代码
pb.FollowMetadata(...)

订阅 A 的 metadata 变化。

收到事件后会执行:

go 复制代码
processEventFnWithOffset
    -> processor.AddSyncJob(resp)

其中 resp 会包含:

  • 源目录
  • 文件名
  • 新旧 entry
  • 文件 chunk 信息
  • 事件时间戳 TsNs
  • signatures

如果是批量事件,客户端还会逐个处理 resp.Events 中的事件。

3. 事件进入 MetadataProcessor

MetadataProcessor 负责:

  • 限制最大并发数;
  • 检查同一路径是否有冲突任务;
  • 控制目录 rename、delete 和文件更新之间的顺序;
  • 启动真正的同步 goroutine;
  • 维护已经完成的 watermark。

核心代码在:

weed/command/filer_sync_jobs.go

当并发达到上限,或者路径冲突时,接收协程会等待:

go 复制代码
for len(t.activeJobs) >= t.concurrencyLimit || t.conflictsWith(resp) {
    t.activeJobsCond.Wait()
}

所以当前实现不是无限制地把所有事件放进一个大队列。

4. filer.sync 根据事件类型更新 B

事件最终进入:

go 复制代码
genProcessFunction(...)

对应代码:

weed/command/filer_sync.go

不同事件的处理方式不同:

新建文件
text 复制代码
读取 A 的 entry
    |
读取 A 的文件 chunks
    |
把 chunks 上传到 B
    |
调用 B 的 CreateEntry

FilerSink.CreateEntry 会:

  1. 查询 B 是否已有相同 entry;
  2. 从 A 读取源 chunk;
  3. 将 chunk 写入 B 的 volume server,或者通过 B filer 写入;
  4. 组装新的 B-side entry;
  5. 调用 B filer 的 CreateEntry

代码位置:

weed/replication/sink/filersink/filer_sink.go

文件更新

更新时通常会:

  1. 查询 B 当前 entry;
  2. 比较 A 的旧 chunks 和新 chunks;
  3. 只复制新增或变化的 chunks;
  4. 删除或保留旧 chunks;
  5. 调用 B 的 UpdateEntry

代码位置:

weed/replication/sink/filersink/filer_sink.go

删除文件

调用 B filer 的删除接口:

go 复制代码
filer_pb.Remove(...)

代码位置:

weed/replication/sink/filersink/filer_sink.go

重命名文件或目录

优先调用 B 的:

go 复制代码
AtomicRenameEntry

如果目标端旧路径不存在,则可能退化为创建新路径。

代码位置:

weed/replication/sink/filersink/filer_sink.go

三、B 更新完成后会不会返回值?

会返回,但这个返回值只返回给 filer.sync,不会返回给 A 上最初写文件的客户端。

例如:

go 复制代码
filer_pb.CreateEntry(...)

或者:

go 复制代码
client.UpdateEntry(...)

底层都是 gRPC 请求,B filer 会返回响应和错误。filer.sync 将其转换成 Go 的 error

text 复制代码
B filer RPC 成功
    -> error == nil
    -> 同步任务成功

B filer RPC 失败
    -> error != nil
    -> 重试
    -> 最终失败则记录失败状态

因此关系是:

text 复制代码
A 写入请求的返回值
    只代表 A 写入是否成功

B 更新 RPC 的返回值
    返回给 filer.sync

filer.sync 的同步结果
    不会回传给 A 原始写入客户端

也就是说,A 的生产者通常无法从自己的写入请求响应中知道 B 是否已经同步完成。

四、它是同步更新还是异步更新?

是异步更新。

典型时间线如下:

text 复制代码
T1 生产者向 A 写入文件
T2  A filer 写入 metadata
T3  A filer 发送 metadata 事件
T4  filer.sync 收到事件
T5  filer.sync 排队并启动任务
T6  filer.sync 从 A 读取 chunk
T7  filer.sync 将 chunk 写入 B
T8  filer.sync 调用 B CreateEntry/UpdateEntry
T9  B filer 返回成功
T10 filer.sync 将该任务标记为完成

所以:

text 复制代码
T1 成功 != T9 已完成

A 写入成功后,B 可能还没有更新完成。

五、失败时会发生什么?

MetadataProcessor 中使用:

go 复制代码
util.Retry("metadata processor", ...)

同步任务失败后会根据 SeaweedFS 的重试策略进行重试。

最终结果分为两种:

text 复制代码
重试后成功
    -> processed_total + 1
    -> pending_tasks - 1

重试后仍失败
    -> failed_total + 1
    -> pending_tasks - 1
    -> watermark 不跨过该失败事件

失败事件会阻止 watermark 越过它,目的是让重启后的 filer.sync 重新从该位置继续处理,而不是永久跳过这个事件。

六、A 会收到 B 的更新确认吗?

不会。

A filer 只负责:

text 复制代码
保存自己的 metadata
向订阅者发送 metadata 事件

它并不知道 filer.sync 是否已经成功更新 B。

A 不会收到类似这样的确认:

text 复制代码
"文件已经成功同步到 B"

除非业务侧自己:

  • 查询 B;
  • 读取 Prometheus 指标;
  • 调用未来设计的同步状态 API;
  • 或自行实现应用层确认机制。

七、双向同步时会不会循环?

双向模式下:

text 复制代码
A -> B
B -> A

B 收到来自 A 的变更后,可能再次产生 metadata 事件。代码使用 Signatures 和 filer signature 判断事件是否来自目标同步链路:

go 复制代码
if sig == targetFilerSignature {
    return nil
}

这样可以跳过由对端同步过来的事件,避免:

text 复制代码
A -> B -> A -> B -> ...
相关推荐
未济1 天前
linux 配置环境变量
linux
傲世仙尊1 天前
目录即文件-Ext文件系统收尾篇
linux·c语言
虎头金猫1 天前
4K 视频总卡在公网带宽?用 N1 + OpenList 把网盘播放链路重新理顺
运维·服务器·网络·python·容器·beautifulsoup·pandas
_艾伦 耶格尔.2 天前
进程间通信
linux
Liuqy-052 天前
Linux IO编程——静态库、动态库
linux
wuyk5552 天前
《WiFi 嵌入式物联网开发全套实战》| 第 16 章 ESP32 AP+STA 双模共存原理与工程坑点
网络·stm32·物联网
彧azz2 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
-梅2 天前
linux(8) 软硬链接
linux·运维·服务器
Wang's Blog2 天前
Java 项目实战: 外卖平台-文件下载与ServletOutputStream回写浏览器
服务器·项目开发
AIgorithmGEEK2 天前
[Linux]线程三部曲(上):一个执行流的诞生——从操作系统一路拆到 pthread_create
linux·线程·pid