# fastsync 技术预研
## 1. 目标与约束
fastsync 的目标不是单纯“复制文件”,而是在保证正确性的前提下,尽可能高效地完成目录差异识别与同步执行。预研时需要同时考虑以下约束:
- 文件数量可能很多,小文件和大文件混合存在。
- 目录遍历、元数据比较、内容校验、文件复制的瓶颈并不相同。
- 文件系统 I/O 属于高延迟操作,盲目增大并发度可能导致磁盘抖动、缓存失效或上下文切换开销上升。
- 同步工具必须优先保证结果可靠,性能优化不能以提高误判风险为代价。
基于这些约束,fastsync 更适合采用“分阶段流水线 + 可控并发 + 分层比较策略”的设计,而不是单一粗暴的全量多线程复制。
## 2. 技术问题拆解
一个目录同步工具通常包含以下关键阶段:
1. 目录扫描:枚举源目录与目标目录的树结构。
2. 差异判定:比较路径、大小、时间戳、权限、哈希等信息。
3. 任务调度:决定哪些文件需要复制、更新、删除或跳过。
4. 执行同步:并发创建目录、复制文件、覆盖目标、清理过期项。
5. 结果校验:确认复制后的文件内容和元数据满足预期。
6. 失败恢复:处理中断、部分成功、目标端残留临时文件等异常情况。
预研重点主要落在第 2、3、4 阶段。
## 3. 多线程并发同步方案调研
### 3.1 单线程串行模型
| 单线程遍历 + 单线程复制 | 逻辑简单,容易验证正确性,调试成本低 | 无法充分利用多核 CPU,也难以隐藏 I/O 等待时间 | MVP 原型、正确性基线、回归对照 |
结论:适合作为基线实现和对照组,但不适合作为最终性能方案。
### 3.2 固定线程池 + 有界任务队列
| 扫描线程生成任务,复制线程池消费任务 | 并发度可控,容易做背压控制,适合 I/O 密集复制 | 需要自行处理任务切分、队列长度、失败传播和退出逻辑 | 中大型目录同步、需要精细控制资源使用 |
分析:
- 该方案最实用,尤其适合同步任务中“扫描速度”和“复制速度”不一致的场景。
- 若使用有界队列,可以避免扫描线程无限推送任务导致内存膨胀。
- 线程池大小可根据 CPU 核数、磁盘类型、文件大小分布动态调整。
推荐工具:
- `crossbeam-channel` 或 `flume`:实现带背压的任务通道。
- `rayon` 也可作为执行层,但任务边界仍建议显式控制。
### 3.3 Rayon 工作窃取模型
| 使用 Rayon 并行遍历或并行处理文件任务 | API 简洁,负载均衡好,对 CPU 密集计算友好 | 对纯 I/O 场景不一定最优,任务顺序和资源控制较弱 | 哈希计算、批量比较、CPU 与 I/O 混合场景 |
分析:
- Rayon 很适合把“待比较文件集合”并行处理,例如批量计算哈希、批量比较元数据。
- 如果直接把文件复制也完全交给 Rayon,可能出现并发度过高、磁盘寻址放大等问题。
- 更稳妥的方式是用 Rayon 处理 CPU 密集阶段,用有界队列控制 I/O 阶段。
### 3.4 Tokio 异步运行时
| 使用 async/await 和 Tokio 管理并发任务 | 统一管理大量任务,网络或混合 I/O 场景扩展性强 | 对本地文件 I/O 的收益有限,实现复杂度更高 | 未来若扩展远程同步、网络传输、服务化接口 |
分析:
- 目录同步的核心瓶颈通常是本地文件系统 I/O,而不是网络套接字。
- 当前项目如果以本地目录同步为主,Tokio 不是首选,会带来更高的工程复杂度。
- 若未来需要支持远程端同步、HTTP 控制接口或对象存储后端,再考虑引入异步运行时更合适。
### 3.5 分阶段流水线模型
推荐把同步过程拆成独立阶段,而不是让一个线程从头处理到尾:
1. 扫描阶段:遍历源目录和目标目录,构建文件条目。
2. 比较阶段:先做路径与元数据比较,筛出疑似变更文件。
3. 哈希阶段:仅对疑似变更文件做内容级校验。
4. 执行阶段:复制、覆盖、删除、创建目录。
5. 校验阶段:必要时做复制后验证。
优点:
- 每个阶段可单独优化与测量。
- 容易对 CPU 密集和 I/O 密集任务采用不同并发策略。
- 错误定位更清晰,便于后续维护和问题复现。
缺点:
- 结构设计复杂度高于简单循环。
- 需要清晰定义阶段间的数据结构和失败传播方式。
结论:这是最适合作为 fastsync 中长期架构的方案。
## 4. 文件内容差异判定策略调研
### 4.1 仅比较路径和文件元数据
| 比较路径、大小、修改时间、权限等元数据 | 速度快、I/O 成本低 | 存在误判风险,时间戳不可靠时可能遗漏内容变化 | 快速扫描、预筛选、非强校验模式 |
说明:
- 这是最常见的第一层过滤手段。
- 不建议把“时间戳相同”直接视为“内容一定相同”,尤其在跨平台、跨文件系统或外部工具修改文件的场景中。
### 4.2 整文件哈希比较
| 对整个文件流式读取并计算哈希值 | 结果稳定,误判率极低,可用于强校验 | 需要完整读取文件,I/O 开销高 | 重要文件校验、最终确认、复制后校验 |
说明:
- 应采用流式读取,避免一次性载入大文件。
- 可只对“元数据有差异”或“策略要求强校验”的文件执行,避免全量哈希导致吞吐下降。
### 4.3 分块哈希或采样哈希
| 读取固定位置或按块计算局部哈希 | 比整文件哈希更快,适合大文件预筛选 | 存在漏判风险,不能作为最终强一致依据 | 大文件快速预判、候选筛选 |
说明:
- 该策略可以作为“快速模式”的一部分,但不适合作为默认强一致方案。
- 若用户要求“结果绝对准确”,仍需回落到整文件哈希或字节级比较。
### 4.4 字节级逐块比较
| 两侧文件同时流式读取并逐块比较 | 无哈希碰撞问题,结论直接 | 需要同时读取两端文件,I/O 压力大,复用性差 | 本地同机双目录精确比较、调试模式 |
说明:
- 从正确性角度非常直接,但工程上通常不如“整文件哈希 + 条件校验”通用。
- 如果未来要支持远端目录或缓存比较结果,字节级逐块比较不如哈希方案灵活。
## 5. 文件哈希算法调研
### 5.1 SHA-256
| 优点 | 安全性强、碰撞风险极低、生态成熟、跨平台兼容性好 |
| 缺点 | 速度相对较慢,对纯性能导向的同步工具成本偏高 |
| 结论 | 适合安全敏感场景,但不一定是本项目默认首选 |
### 5.2 BLAKE3
| 优点 | 速度很快,支持并行计算,安全性强,适合大文件和批量文件校验 |
| 缺点 | 相比 SHA-256,在部分传统工具链中的“默认认知度”稍低 |
| 结论 | 非常适合作为 fastsync 的默认强校验哈希算法 |
### 5.3 xxHash
| 优点 | 极快,CPU 开销低,适合做预筛选或缓存键 |
| 缺点 | 非加密哈希,碰撞风险高于加密哈希,不适合作为最终一致性依据 |
| 结论 | 可作为快速模式或内部性能优化工具,不建议作为最终校验依据 |
### 5.4 CRC32
| 优点 | 实现简单、速度快、兼容历史工具 |
| 缺点 | 抗碰撞能力弱,误判风险高 |
| 结论 | 不建议用于目录同步的最终内容判定 |
### 5.5 哈希算法选择建议
建议采用分层策略:
1. 第一层:路径、大小、修改时间等元数据快速过滤。
2. 第二层:对疑似变化文件使用 BLAKE3 做内容级确认。
3. 可选快速模式:对大批量低风险场景,可允许使用 xxHash 作为预筛选,但最终仍应支持回退到 BLAKE3。
原因:
- 只用元数据不够可靠。
- 直接全量 BLAKE3 正确但成本偏高。
- “元数据预筛 + 条件哈希确认”能在正确性与性能之间取得较好平衡。
## 6. 复制与落盘策略调研
### 6.1 直接覆盖写入
| 实现简单,代码路径短 | 失败时可能留下半写入文件,不利于恢复 |
结论:不建议作为默认策略。
### 6.2 临时文件 + 原子重命名
| 更安全,失败时更容易清理,能降低目标文件损坏风险 | 实现稍复杂,需要管理临时文件和跨文件系统行为 |
结论:应作为默认写入策略。
### 6.3 保留元数据
需要明确是否同步以下属性:
- 修改时间
- 权限位
- 属主与属组
- 符号链接属性
优点:目标目录更接近源目录。
缺点:
- 跨平台和跨文件系统时兼容性复杂。
- 属主与权限同步可能需要额外权限。
建议:MVP 先支持基础时间戳和权限同步,属主与高级属性作为后续能力。
## 7. 目录遍历与任务组织调研
### 7.1 遍历方案
| `walkdir` | 生态成熟、简单稳定 | 默认是串行遍历 |
| `jwalk` | 并行遍历性能更好,适合大目录 | 复杂度稍高,对顺序控制不如串行直观 |
建议:
- MVP 用 `walkdir` 建立正确性基线。
- 当大目录扫描成为明显瓶颈时,再评估切换到 `jwalk`。
### 7.2 任务表示
建议显式建模同步任务,而不是到处直接操作路径字符串。可以考虑以下任务类型:
- 创建目录
- 复制新文件
- 覆盖已有文件
- 删除过期文件
- 校验复制结果
优点:
- 方便统计、重试、日志输出和测试。
- 能清楚区分“发现差异”和“执行差异”两个阶段。
## 8. CLI 可选参数与默认值设计建议
### 8.1 设计原则
在 CLI 设计上,建议遵循以下原则:
- 默认值优先保证安全,尤其是删除、覆盖、跟随符号链接等高风险行为。
- 参数语义尽量显式,避免把多个行为揉进一个含义模糊的开关中。
- 同时兼顾交互式终端用户和脚本调用方,保证人可读输出与机器可读输出都可用。
- 将参数按“同步语义、比较策略、性能控制、输出行为”分组,降低学习成本。
在需求未完全冻结前,下面内容应视为“建议参数集与推荐默认值”,用于指导后续实现和讨论,而不是立即锁死最终 CLI 语义。
### 8.2 建议命令形态
建议核心命令形态保持简洁:
```text
fastsync <source> <target> [options]
```
其中:
- `source` 和 `target` 作为必填位置参数。
- 其他行为全部通过可选参数显式控制。
- 高风险能力不应依赖隐式默认模式开启。
### 8.3 建议参数枚举
#### 同步语义与比较策略
| `--dry-run` | `false` | 仅输出计划执行的操作,不实际修改目标目录,适合上线前预演。 |
| `--delete` | `false` | 是否删除目标端多余文件。默认关闭,避免误删。 |
| `--follow-symlinks` | `false` | 是否跟随符号链接。默认关闭,降低循环链接和越权访问风险。 |
| `--compare <auto | metadata | hash>` | `auto` | `auto` 表示先比较元数据,再按策略对疑似变化文件做哈希确认。 |
| `--hash <blake3 | sha256 | xxhash>` | `blake3` | 当需要内容校验时使用的哈希算法。默认推荐 BLAKE3。 |
| `--verify <none | changed | all>` | `changed` | 复制完成后的验证强度。默认只验证发生变化的文件。 |
| `--preserve-times <true | false | auto>` | `auto` | 自动按平台能力决定是否保留时间戳。 |
| `--preserve-permissions <true | false | auto>` | `auto` | 自动按平台能力决定是否保留权限位。 |
| `--atomic-write <true | false>` | `true` | 是否使用“临时文件 + 原子重命名”写入目标。建议默认开启。 |
#### 性能与范围控制
| `--threads <N | auto>` | `auto` | 默认建议为 `min(available_parallelism, 8)`,兼顾吞吐与磁盘压力。 |
| `--queue-size <N>` | `threads * 4` | 有界任务队列长度,避免扫描结果无限堆积。 |
| `--exclude <pattern>` | 空 | 可重复指定,优先用于排除缓存、临时目录等路径。 |
| `--include <pattern>` | 空 | 默认为全量路径;当指定后,仅同步匹配项。 |
| `--max-errors <N>` | `100` | 达到阈值后中止,防止错误持续滚动放大。 |
| `--stop-on-error` | `false` | 是否在首个错误后立即退出。默认继续收集错误并给出汇总。 |
#### 输出与可观测性
| `--log-level <error | warn | info | debug | trace>` | `info` | 控制日志详细程度。 |
| `--color <auto | always | never>` | `auto` | 在支持彩色的终端上启用彩色输出,否则降级为纯文本。 |
| `--progress <auto | always | never>` | `auto` | 仅在交互式终端中展示动态进度。 |
| `--output <text | json>` | `text` | 终端默认文本输出;自动化场景可切换 JSON。 |
| `--config <path>` | 空 | 指定外部配置文件,便于保存常用参数集。 |
### 8.4 推荐默认值背后的取舍
- 删除默认关闭:目录同步工具最容易出事故的地方就是删除策略,默认不删是更稳妥的安全基线。
- 比较策略默认 `auto`:直接全量哈希成本太高,只比元数据又不够稳;采用分层策略更平衡。
- 哈希算法默认 `blake3`:在正确性足够强的前提下,性能明显优于 SHA-256,更适合本项目。
- 验证默认 `changed`:兼顾结果可靠性和运行成本,避免每次都对全部文件重复校验。
- 线程默认 `auto`:对本地磁盘 I/O,不宜简单等于逻辑核数,保守上限更容易得到稳定吞吐。
- 彩色与进度默认 `auto`:交互式体验更好,同时不污染重定向输出和日志文件。
### 8.5 参数来源优先级建议
建议参数生效优先级如下:
1. 命令行显式参数
2. 配置文件参数
3. 内建默认值
这样可以保证:
- 脚本或一次性命令可以方便覆盖配置文件。
- 团队可以维护一份共享配置,而不会阻碍临时调试。
- 默认值始终作为最后兜底,不需要每次都传完整参数集。
## 9. 日志输出与终端体验优化
### 9.1 设计目标
日志与终端输出不仅用于排错,也直接影响工具的可维护性和使用体验。建议目标如下:
- 人类用户能快速看懂当前进度、关键结果和失败原因。
- 自动化脚本能稳定消费结构化输出。
- 并发场景下日志不乱序、不互相覆盖、不把终端刷花。
- 彩色输出只在合适的环境中启用,不影响重定向和文件落盘。
### 9.2 日志技术方案对比
| 直接使用 `println!` / `eprintln!` | 最简单,无额外依赖 | 难以分级、难做结构化字段、并发输出容易混乱 | 原型验证 |
| `log` + `env_logger` | 接入成本低,生态广 | 结构化能力一般,对并发上下文表达较弱 | 轻量 CLI 工具 |
| `tracing` + `tracing-subscriber` | 支持结构化字段、span、线程上下文、文本/JSON 双输出 | 心智负担略高,实现会比简单日志更复杂 | 推荐的中长期方案 |
结论:如果 fastsync 后续要支持并发调度、阶段统计、JSON 输出和更细致的诊断信息,`tracing` 更适合作为主日志方案。
### 9.3 输出通道建议
建议区分不同输出通道,而不是所有信息都直接打印到标准输出:
- `stderr`:运行过程日志、警告、错误、动态进度。
- `stdout`:最终摘要,或在 `--output json` 时输出结构化结果。
这样做的好处是:
- 脚本可以直接读取 `stdout` 的机器可读结果。
- 用户仍能在终端中看到实时进度和诊断信息。
- 将来接入日志重定向、文件输出或上层调度器时更容易整合。
### 9.4 彩色输出策略
建议默认采用 `--color auto`,并按以下策略决定是否启用彩色输出:
1. 当前输出目标是交互式终端,而不是普通文件或管道。
2. 未被 `NO_COLOR` 或 `CLICOLOR=0` 显式禁用。
3. 若设置了 `CLICOLOR_FORCE=1`,则允许强制启用颜色。
4. 遇到 `TERM=dumb` 等明显不支持 ANSI 的环境时自动降级。
推荐实现方向:
- 使用 `anstream` / `anstyle` 处理终端能力判断和 ANSI 输出细节。
- 若只是做轻量着色,也可用 `owo-colors`,但需自行保证开启条件判断正确。
建议颜色语义保持稳定:
- 成功:绿色
- 警告:黄色
- 错误:红色
- 进行中状态或进度:青色或蓝色
- 最终摘要标题:加粗
同时要注意:颜色只能增强识别,不能成为唯一信息来源。即使关闭颜色,也应保留如 `INFO`、`WARN`、`ERROR` 这类文本前缀。
### 9.5 动态进度与并发日志协同
并发同步场景下,日志和进度最容易互相打架。建议采用以下策略:
- 仅在 `--progress auto/always` 且输出为 TTY 时启用动态单行进度。
- 非 TTY 场景下禁用刷新式进度条,改为周期性摘要日志。
- 由单独的输出协调层统一渲染进度和日志,避免多个 worker 线程直接写终端。
- `debug` 或 `trace` 级别下,应优先保证日志完整可读,必要时降低动态刷新频率。
推荐展示的进度指标包括:
- 已扫描文件数与目录数
- 已复制、已跳过、已删除数量
- 当前吞吐量,如文件数每秒、字节数每秒
- 活跃 worker 数量与队列积压情况
- 预计剩余时间(若能可靠估算)
### 9.6 终端摘要与 JSON 输出建议
建议无论是否开启详细日志,任务结束后都输出一份稳定摘要,至少包括:
- 源目录与目标目录
- 总扫描文件数、变更文件数、跳过文件数、删除文件数
- 总复制字节数
- 总耗时
- 错误数与首个关键错误摘要
当启用 `--output json` 时,建议:
- 禁用动态进度条
- 避免在 `stdout` 混入人类文本日志
- 保持字段名稳定,便于后续脚本与 CI 集成
### 9.7 日志默认策略建议
当前阶段建议默认行为如下:
- `--log-level info`
- `--color auto`
- `--progress auto`
- `--output text`
- 运行结束始终输出摘要
这组默认值的优点是:交互式使用足够友好,脚本接入成本也较低,同时不会在不支持彩色或不支持动态刷新的环境中制造噪声。
## 10. 推荐技术路线
### 10.1 第一阶段:正确性优先的 MVP
建议路线:
1. 使用 `walkdir` 完成源目录和目标目录扫描。
2. 先基于路径 + 元数据做差异判定。
3. 对疑似变更文件使用 BLAKE3 做确认。
4. 使用“临时文件 + 重命名”执行复制。
5. 并发层采用固定线程池 + 有界队列,先保持保守并发度。
优点:
- 正确性和实现复杂度平衡较好。
- 易于继续扩展日志、进度、重试和删除策略。
### 10.2 第二阶段:性能优化
在有真实数据和基准测试之后,再考虑:
- 引入并行遍历。
- 根据文件大小做分级调度,小文件批量处理,大文件限制并发。
- 为哈希结果建立缓存,减少重复计算。
- 针对同文件系统场景评估 reflink、硬链接或零拷贝优化可能性。
### 10.3 第三阶段:高级同步能力
可进一步研究:
- 类 rsync 的块级差量同步。
- 断点恢复与任务持久化。
- 双向同步与冲突检测。
- 远程节点同步。
## 11. 主要风险与注意事项
- 文件在同步过程中被外部进程修改,可能导致扫描结果失效。
- 删除策略若设计不清,会引入高风险的数据丢失问题。
- 大量小文件场景下,系统调用开销和目录遍历成本可能超过复制本身。
- 大文件场景下,哈希和复制都可能成为瓶颈,需要避免重复读取。
- 符号链接、循环链接、权限不足、跨文件系统重命名失败都需要明确策略。
- 参数过多且语义重叠会显著提升 CLI 学习成本,因此需要尽量保持参数边界清晰。
- 彩色输出和动态进度若未正确检测终端能力,可能污染日志文件或影响脚本解析。
- 多线程 worker 直接竞争终端输出会导致日志交错,必须有统一的输出协调机制。
## 12. 最终建议
对于 fastsync,当前最稳妥的技术路线是:
- 架构上采用“扫描 -> 比较 -> 哈希确认 -> 并发执行 -> 校验”的分阶段流水线。
- 并发上采用“固定线程池 + 有界队列”的可控模型,而不是一开始就全面异步化。
- 内容判定上采用“元数据预筛 + BLAKE3 强校验”的分层策略。
- 写入策略上采用“临时文件 + 原子重命名”,优先保证目标目录安全性。
- CLI 默认值上坚持“安全优先、自动降级、显式覆盖”的原则,例如删除默认关闭、颜色与进度默认自动检测。
- 日志体系上优先预留结构化输出能力,并在支持彩色的终端中提供克制、稳定的彩色反馈。
这一路线能在工程复杂度、性能潜力与结果可靠性之间取得较好的平衡,适合作为本项目后续正式编码实现的基础方案。