Yisync 是一个 C++20 文件同步原型。当前目标不是做完整 rsync,而是把下面这条核心链路跑清楚:
Sender(A) 扫描源目录
Sender 发送 Manifest1
Receiver(B) 扫描目标目录并计算 Manifest2
Receiver 告诉 Sender 哪些 entry 要传、从哪里续传
Sender 读取真实文件并发送 CREATE/DATA 或 FILE_BEGIN/CHUNK/FILE_COMMIT
Receiver 写入目标目录
当前最重要的约束:
- Sender 不做本地持久化同步进度。
- Receiver 的最终目录是恢复依据。
- chunk 临时区
.yisync_tmp不做持久恢复;Receiver 创建 chunk stream 时会清理旧临时区。 - 同一个 stream 内 entry 严格按
seq对外可见。 - 不同 stream 可以并行。
- 当前只支持新增文件、目录、软链接和 append;不支持删除、重命名、原地覆盖、rsync delta。
- readme.md:项目入口、构建、运行、配置、测试。
- protocol.md:wire 协议、消息字段、状态规则。
- detail.md:代码结构、模块边界、端到端流程。
- todo.md:只记录未完成事项。
crc32c/ 下是第三方 Google CRC32C 库文档,不属于 Yisync 自己的设计文档。
已经实现:
- 独立
sender/receiver进程。 - 基于
poll的异步 TCP event loop。 - 多 TCP line 连接、断线检测、自动重连。
- 每条 line 独立 token bucket 限速、接收窗口背压、in-flight 跟踪、健康状态选择。
HELLOversion / capability negotiation。- Sender 启动后先发送
MANIFEST1。 - Receiver 基于
MANIFEST1和目标目录返回MANIFEST2。 - Sender 根据
MANIFEST2生成真实发送计划。 - 真实源目录扫描、真实文件 reader。
- 多文件连续发送。
- 多 stream:配置多个源目录会映射为不同 stream;同 stream 严格顺序,不同 stream 可并行。
- 目录、空目录、普通文件、软链接同步。
- 小文件和 append 使用
CREATE + DATA,单条DATA默认最大 64KB,实际跟随chunk_size配置。 - 大于 64KB 的缺失文件使用 chunk 模式,默认每 chunk 64KB。
- chunk 可乱序到达,Receiver 写
.yisync_tmp后在FILE_COMMIT做整文件 CRC32C、rename、fsync。 - Receiver 后台 disk writer 执行 append fsync 和 chunk commit。
HEARTBEAT作为批量 ACK,同时携带接收窗口、已接收 chunk、缺失 chunk hint、append durable offset。- Sender 当前进程内保存未确认发送缓冲,
NACK先查缓冲重发。 - 断线 lost-send 回调、missing hint、动态 RTO、Manifest 恢复、最终失败上报。
config.txt配置多 line、多源目录、正则过滤、限速、窗口、重连、chunk size、heartbeat 等参数。watch=true长期运行:Linux 使用inotify,macOS 使用FSEvents,不可用时 fallback polling。- Google CRC32C。
- CTest 自动化测试,覆盖单元测试和真实 A/B 进程集成测试。
未实现或明确不做:
- 删除、重命名、原地修改、rsync delta 当前场景不需要。
- UDP / QUIC / AREON adapter 只有接口预留。
- LZ4 / Zstd / MD5 只有枚举预留。
- 配置热更新不支持,修改配置必须重启。
- chunk 不做掉电级持久恢复。
cmake -S . -B build-cpp20
cmake --build build-cpp20构建产物:
build-cpp20/yisync_sender Sender 进程
build-cpp20/yisync_receiver Receiver 进程
build-cpp20/yisync_unit_tests C++ 单元测试
build-cpp20/yisync_fault_client 测试专用坏消息客户端
ctest --test-dir build-cpp20 --output-on-failureCTest 当前包含 10 个测试项:
- 协议 round-trip decode。
- malformed frame fuzz smoke test。
HELLOnegotiation。- chunk resend / missing hint 状态。
- network scheduler / line health / lost-send。
- T_ReceiverCoordinator / SPSC disk writer。
- chunk commit 失败、append fsync 失败。
- 真实 A/B 进程同步。
- 断线重连、receiver 重启、目录/软链接、多 stream、限速背压、Manifest 恢复、最终失败。
- fault client wire 级构造
BAD_CHECKSUM、BAD_COMMIT、SIZE_CONFLICT。
真实 A/B 集成测试需要本机 TCP listen/connect 权限。受限 sandbox 里可能出现 Operation not permitted;在允许本机端口的环境中运行即可。
先启动 Receiver:
./build-cpp20/yisync_receiver \
--host 127.0.0.1 \
--base-port 19000 \
--lines 2 \
--root /tmp/yisync_receiver再启动 Sender,使用真实源目录:
mkdir -p /tmp/yisync_source
python3 - <<'PY'
from pathlib import Path
Path('/tmp/yisync_source/demo.bin').write_bytes(b'A' * 153600)
PY
./build-cpp20/yisync_sender \
--host 127.0.0.1 \
--base-port 19000 \
--lines 2 \
--source-stream /tmp/yisync_source/tmp/yisync_source 中可以放:
- 普通文件
- 子目录
- 空目录
- 软链接
软链接会复制链接本身,不会跟随链接目标。
也可以用配置文件启动:
./build-cpp20/yisync_receiver --config config.txt
./build-cpp20/yisync_sender --config config.txt配置示例:
[common]
link_num=2
Bandwidth_Limit={96KB,96KB}
recv_window=192KB
chunk_size=64KB
heartbeat_interval_ms=50
heartbeat_ack_batch_size=20
heartbeat_timeout_ticks=300
chunk_retransmit_ticks=100
max_retransmit_retries=5
max_manifest_recovery_attempts=3
max_missing_ranges=64
reconnect_base_delay_ms=100
reconnect_max_delay_ms=2000
watch=false
watch_backend=auto
watch_interval_ms=500
watch_rescan_debounce_ms=200
compress=none
checksum=crc32c
[sender]
ip=127.0.0.1
port=19000
high_priority_dirs={/private/tmp/yisync_multi_a::.*\.(file|bin)$,/private/tmp/yisync_multi_b::.*\.(keep|dat)$}
low_priority_dirs={/tmp/source_c}
[receiver]
ip=127.0.0.1
port=19000
mount_dir=/tmp/yisync_receiver目录配置规则:
high_priority_dirs和low_priority_dirs是 Sender 要同步的源目录列表。- 多个目录写在同一个
{}里,用逗号分隔。 - 每个目录可以追加
::regex。 - regex 对源目录内相对路径做
regex_search,接近grep -E。 - 不写 regex 表示同步该目录下所有 entry。
- 目录 entry 会保留,用于创建目标目录结构;普通文件和软链接按 regex 过滤。
- Receiver 只配置
mount_dir。 - Receiver 收到
MANIFEST1后,按 Sender 源目录的绝对路径挂载到mount_dir下。
例子:
[sender]
high_priority_dirs={/private/tmp/yisync_multi_a::.*\.(file|bin)$,/private/tmp/yisync_multi_b::.*\.(keep|dat)$}
[receiver]
mount_dir=/private/tmp/yisync_multi_receiver最终写入:
/private/tmp/yisync_multi_receiver/private/tmp/yisync_multi_a/...
/private/tmp/yisync_multi_receiver/private/tmp/yisync_multi_b/...
配置只在启动时读取,不支持热更新。
配置多个源目录时,每个目录会映射为一个 stream。默认 stream id 是 9001。
也可以重复指定 --source-stream 来同步多个目录:
mkdir -p /tmp/yisync_source_1 /tmp/yisync_source_2
./build-cpp20/yisync_sender \
--host 127.0.0.1 \
--base-port 19000 \
--lines 2 \
--source-stream /tmp/yisync_source_1 \
--source-stream /tmp/yisync_source_2对应关系:
/tmp/yisync_source_1 -> stream 9001
/tmp/yisync_source_2 -> stream 9002
Receiver 目标目录布局:
默认 stream 9001 -> /tmp/yisync_receiver/<相对路径>
其他 stream N -> /tmp/yisync_receiver/N/<相对路径>
watch=true 时 Sender 和 Receiver 都会长期运行。
Sender watcher 看到新增文件或 append 变长后,不直接生成单条操作,而是:
触发 rescan
重新发送 Manifest1
Receiver 重新计算 Manifest2
Sender 只补新增文件或 append 区间
当前 watcher 不生成删除、rename、原地覆盖协议。
可以让 Sender 主动断开一次指定 line:
mkdir -p /tmp/yisync_source
python3 - <<'PY'
from pathlib import Path
Path('/tmp/yisync_source/reconnect.bin').write_bytes(b'B' * 2097152)
PY
./build-cpp20/yisync_sender \
--host 127.0.0.1 \
--base-port 19000 \
--lines 2 \
--source-stream /tmp/yisync_source \
--drop-line-once 1预期现象:
Sender 连接多条 TCP line
Sender 发送 FILE_BEGIN
Sender 将 CHUNK 分发到不同 line
Receiver 乱序接收 CHUNK 并写入 .yisync_tmp
Receiver 批量 HEARTBEAT 返回 received_chunks / missing_ranges
Sender 释放已确认 in-flight
Sender 发送 FILE_COMMIT
Receiver 后台 writer 校验、rename、fsync
Receiver 在 commit 完成后发送最终 HEARTBEAT
flowchart LR
A["Sender"] --> M1["Manifest1"]
M1 --> B["Receiver"]
B --> M2["Manifest2"]
M2 --> A
A --> P["Send plan"]
P --> R["Source reader"]
R --> N["T_SenderNetwork"]
N --> S["Scheduler + line health"]
S --> L1["TCP line 1"]
S --> L2["TCP line 2"]
L1 --> B
L2 --> B
B --> T[".yisync_tmp"]
T --> F["Final directory"]
B -- "HEARTBEAT / NACK" --> N
src/ 和 include/ 使用同样的模块分层:
common/:wire 协议、manifest/diff、checksum、chunk 策略、源目录 reader、CLI 公共解析。network/:event loop、异步 TCP、line 状态、重连、调度、限速、背压。sender/:source watcher、发送计划、chunk resend、发送缓冲、T_SenderApp、Sender 命令入口。receiver/:append/chunk receiver、stream map、coordinator、disk writer、commit poller、T_ReceiverApp、Receiver 命令入口。
命名规则:
- 函数、变量、成员变量、文件名使用
snake_case,成员变量末尾加_。 - 常量使用
kUpperCamelCase。 - 项目自有业务类型使用大驼峰并加
T_前缀,例如T_FileSendTask。 - 项目枚举类型使用
EM_前缀,例如EM_NackReason。 - 枚举值使用全大写下划线,例如
BAD_CHECKSUM。 - wire protocol 里的消息名仍按协议文档写作
MANIFEST1、FILE_BEGIN等,不等同于 C++ 类型命名。
常用入口:
| 文件 | 说明 |
|---|---|
include/common/protocol.hpp |
wire 消息、枚举、frame、CRC32C 接口 |
src/common/protocol.cpp |
消息编解码、frame 编解码 |
include/common/sync.hpp |
manifest 扫描、diff、chunk 策略接口 |
src/common/sync.cpp |
manifest/diff/checksum/chunk 策略实现 |
include/network/network.hpp |
T_SenderNetwork / T_ReceiverNetwork |
src/network/network.cpp |
TCP line、重连、accept、heartbeat 聚合、line state 日志 |
include/network/scheduler.hpp |
多线路调度、令牌桶、背压接口 |
src/network/scheduler.cpp |
限速、选线、in-flight 释放、lost-send |
include/common/reader.hpp |
源目录 reader 和 manifest scan 接口 |
src/common/reader.cpp |
源目录扫描、regex 过滤、真实文件读取、checksum |
include/sender/source.hpp |
源目录 watcher 抽象 |
src/sender/source.cpp |
watcher backend: inotify/FSEvents/polling |
include/sender/sender_plan.hpp |
T_FileSendTask / T_StreamSendState / Manifest2 应用 |
src/sender/sender_plan.cpp |
task 构建、payload 读取 |
include/sender/append_state.hpp |
append 续传状态、heartbeat 推进、断线匹配 |
src/sender/append_state.cpp |
append plan 初始化、ack、重传状态更新 |
include/sender/chunk_resend.hpp |
chunk resend / missing hint 状态 |
src/sender/chunk_resend.cpp |
chunk ack、missing hint、lost chunk、重传尝试 |
include/sender/send_buffer.hpp |
当前进程发送缓存和重传队列 |
src/sender/send_buffer.cpp |
HEARTBEAT 释放缓存、NACK/LostSend 查缓存重传 |
src/sender/sender_app.cpp |
Sender 命令入口和进程主逻辑 |
include/receiver/receiver.hpp |
append receiver 和 chunk receiver 状态机 |
src/receiver/receiver.cpp |
Receiver 写盘、chunk 乱序接收、commit |
include/receiver/receiver_coordinator.hpp |
Receiver action 协调接口 |
src/receiver/receiver_coordinator.cpp |
CREATE/DATA/FILE_BEGIN/CHUNK/FILE_COMMIT 协调 |
include/receiver/disk_writer.hpp |
bounded SPSC 后台 writer |
src/receiver/disk_writer.cpp |
writer 线程、队列、异常记录 |
src/receiver/receiver_app.cpp |
Receiver 命令入口和进程 glue |
include/common/cli_common.hpp |
CLI options、默认参数、公共 helper 接口 |
src/common/cli_common.cpp |
CLI 参数解析和公共 helper 实现 |
include/common/config_loader.hpp |
配置文件入口 |
src/common/config_loader.cpp |
config.txt 解析和 options 应用 |
建议阅读顺序见 detail.md。