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 -> ...
相关推荐
Huangjin007_14 分钟前
【Linux 系统篇(十六)】进程(四) : 僵尸进程、孤儿进程、进程优先级
linux·运维·服务器
IvorySQL22 分钟前
PostgreSQL 日报|备用服务器 FSM 数据不一致(9 月 2 日)
服务器·数据库·postgresql
艾芯微科技25 分钟前
B1040A2|DFN1006‑2L 超微型肖特基二极管,便携设备高频整流优选国产器件
网络·单片机·嵌入式硬件·集成测试·51单片机
瞬间&永恒~28 分钟前
【Kubernetes】(十五)维护与升级、ETCD 备份和恢复
linux·运维·docker·云原生·kubernetes
SKH.35 分钟前
网络(2)UDP通信
网络·单片机·udp
susplus37 分钟前
【linux应用软件编程】进程间的通信方式2【网络通信从入门到UDP编程】
linux·udp·ip
MSTcheng.38 分钟前
【Linux】Linux学习第三弹——Linux基本指令2
linux·windows·学习·ubuntu·操作系统
希望奇迹很安静44 分钟前
Linux基本使用命令
linux·运维·服务器
zcmodeltech44 分钟前
风力发电沙盘模型控制系统设计与灯光联动实现方案——基于STM32与Modbus RTU,服务范围覆盖全国的多场景风力发电沙盘模型定制
网络·数据库·stm32·单片机·嵌入式硬件·能源