当前位置: 首页 > news >正文

Cloudflare Computer同步协议30分钟深入:从ChangeEntry到applyChanges全流程

Cloudflare Computer同步协议30分钟深入:从ChangeEntry到applyChanges全流程

【免费下载链接】computerGive your agent a computer 👾项目地址: https://gitcode.com/GitHub_Trending/computer1/computer

Cloudflare Computer 是一个跑在 Cloudflare Durable Object 里的虚拟文件系统,它的**同步协议(Sync Protocol)**负责把云端 SQLite 文件树与沙箱容器里的 FUSE 挂载双向同步。这篇文章带你用 30 分钟走完全流程:从一条ChangeEntry记录如何产生、如何在网络上流转,到最终被applyChanges写进本地存储的每一步,帮你彻底搞懂这套增量双向同步的设计。

同步协议要解决什么问题?

Cloudflare Computer 的架构里,同一个文件系统有两份副本

  • DO 侧(Durable Object):基于 SQLite 的虚拟文件系统,是重启后仍然存在的"事实来源"(source of truth);
  • 容器侧:通过 FUSE 挂载暴露给沙箱的真实文件系统,供computerd守护进程和 Agent 使用。

难点在于:两边都可能随时被修改——你在容器里跑npm install,DO 侧也可能会收到外部写入。如果每次都全量传输文件树,网络成本会爆炸。

所以同步协议是增量的、双向的:每侧都维护一个单调递增的修订号计数器(rev),双方只交换"对方还没见过的那部分变化"。

核心构件:ChangeEntry 同步记录

整套协议的"最小传输单元"就是 ChangeEntry,一条描述"某个路径的最终状态"的记录。它有四种形态:

类型携带的信息说明
file路径、权限、mtime、大小、分块哈希列表文件内容不在记录里,只有每块的sha256哈希
dir路径、权限、mtime目录元数据
symlink路径、链接目标、权限符号链接永不解引用
delete路径、修订号删除操作的"墓碑"记录

两个关键设计值得新手特别注意:

  1. 基于最终状态,而非操作日志。协议里没有rename这类操作指令——目录改名只是把子树里每个 inode 都盖上新的修订号,再为旧路径补一条墓碑。这样接收方不需要按顺序重放操作,重复应用也天然幂等。
  2. 字节从不内联传输。文件按固定 512 KiB 分块,每块用内容寻址(sha256)。同一内容(比如node_modules里被多处引用的库)无论在几个路径出现,网络上只传一次。

一条ChangeEntry是怎么来的?当路径被修改后,materialiseChange 会读取该路径的当前状态——存活的 inode 优先于墓碑,正确处理了"删除后重建"的场景——然后把它打包成线上记录。

Push 流程:DO 如何把变更推给容器

每次执行命令前,DO 会先做一次push,把容器还没见过的变更流过去。流程分三步:

  1. 合并(coalesce)。coalesceChanges 扫描修订号区间内的所有脏路径,同一路径合并成一条(最新状态胜出)——两次执行命令之间重写同一个文件 5 次,网络上只花 1 条记录。条目按rev升序、路径升序输出,为后面的断点续传铺路。
  2. 哈希探测(hasObjects)。发送方先问接收方:"这些块哈希你有哪些?"——相当于 git 的have协商,用一次集合差运算就跳过所有已有字节,没有任何多余的元数据往返。
  3. 补传对象(pushObjects)+ 推条目流(push)。只传输缺失的块,然后流式发送合并后的ChangeEntry批次。

接收方(容器)把整批变更放进单个同步事务里原子应用——半途失败就整批回滚,接收方永远不会看到"半截"的推送。

Pull 全流程:fetchChanges 到 applyChanges

命令执行完后,方向反过来:DO 把容器里 FUSE 捕获的写入拉回来。这是 pullOnce 实现的六步循环,也是全文的精华:

步骤动作一句话解释
① FetchfetchChanges({ after: 游标 })从上次停下的(rev, path)游标处,按修订号排序流出ChangeEntry
② 批处理缓冲最多 256 条(PULL_BATCH_SIZE峰值内存被批大小封顶,而不是整个流
③ Diff探测本地vfs_blobs+fetchObjects拉取缺失块32 字节哈希的集合差,零内容重复传输
④ Apply批次交给applyChanges逐条写入 SQLite,见下方详解
⑤ 检查点游标推进到该批最后一条的(rev, path)崩溃后从最后提交批次恢复,最多重做 256 条
⑥ 循环回到 ② 处理下一批直到流耗尽,最后写入currentCursor

applyChanges 的三条防御线

applyChanges 是变更真正落地的地方,它内建了三道安全机制:

  • 幂等跳过(alreadyApplied)。应用前逐条比对本地状态:文件比 manifest 哈希,目录比权限位,符号链接比目标+权限。已经一致的条目直接丢弃——这让"重复拉取"既便宜又无副作用,是断点恢复能安全工作的根基。
  • 只读挂载守卫。落在只读挂载点下的条目不会被应用,而是收集进skipped返回给调用方,保证挂载点内容不被静默篡改。
  • 结构冲突清理。当远端发来一个文件、而本地该路径是个目录时,接收方会删掉本地子树再应用远端条目——这是**最后写入者胜出(last-writer-wins)**的冲突处理,树永远收敛,但不会做内容合并。

水位线(Watermark):崩溃恢复的秘密

协议双方交换的"进度表"有五个水位线:pushRev(DO 已推到的修订号)、fetchCursor(DO 已拉取到的容器游标)、currentRev(本地最新修订号)、appliedPushCursor(接收方已应用到的推送游标)。

两个亮点:

  1. (rev, path)双坐标游标。一个大的目录改名可能在单个rev内产生成百上千条记录。纯数字游标崩溃后只能重放整个 rev;加入path坐标后,崩溃可以在 rev 内部精确恢复,而不用伪造从未整体提交的"幽灵修订号"。详见 watermarks.ts。
  2. 跨侧不变量。每次 push 和 pull 的响应都会回声接收方的appliedPushCursor,发送方断言它覆盖了自己刚推的修订号。一旦断言失败,说明接收方丢过状态——协议选择大声失败而不是静默腐坏。

重连场景更贴心:reconcileWatermarks 在连接建立时先问对方"你到哪了",发现落后就把本地游标归零,从 rev-0 基线全量重发——由于幂等检查,重复传输几乎零成本。

并发与冲突:什么时候安全?

  • 单个 Workspace 内:所有写入被 DO 运行时串行化,不存在写-写冲突;
  • 两个容器共享一个 Workspace:可能出现真冲突,语义是同步粒度上的 last-writer-wins——最后到达 DO 的推送胜出,先写的变更会被静默覆盖。这和"无锁 NFS 挂载"同语义。

实践建议(来自官方文档):一个 workspace 同时只有一个活跃写者、给多 Agent 划分互不相交的子树、把共享状态放到 DO 的 RPC 面上而不是共享文件里。完整的冲突语义分析见 docs/02_sync_protocol.md。

另一个对新手很实用的机制是ignore 列表:默认排除node_modules,否则一次npm install会把数万个小文件推进同步网络。被忽略的路径在容器内照常工作,只是字节永远不上线。

想继续深挖?三个入口

  • 协议规范与全部设计取舍(为什么不要 rename 操作码、为什么借鉴 git 的 haves/wants):docs/02_sync_protocol.md
  • 线上记录的定义与物化逻辑:packages/dofs/src/sync/changes.ts
  • 批量应用与幂等检查的完整实现:packages/dofs/src/sync/apply.ts
  • 拉取/推送/水位线对账的驱动循环:packages/rpc/src/sync-driver.ts
  • 文件系统表结构与 rev 递增机制:docs/03_filesystem_schema.md

小结:Cloudflare Computer 的同步协议用四个概念解决了"两份文件树如何廉价且安全地收敛"——ChangeEntry状态化记录、内容寻址分块、(rev, path)断点游标、applyChanges幂等应用。它不追求合并冲突,而是用幂等和最终状态保证任何崩溃、重连、重复传输下系统都能收敛到正确状态。这正是分布式文件系统"简单优于聪明"的经典取舍。

【免费下载链接】computerGive your agent a computer 👾项目地址: https://gitcode.com/GitHub_Trending/computer1/computer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

http://www.cnnetsun.cn/news/4309097.html

相关文章:

  • Wi-Fi室内定位实战指南:不依赖UWB/蓝牙的低成本部署方案
  • 全桥峰值电流控制实战:LAT1319 Push-Pull模式斜坡补偿与调试
  • 一周入门大模型:从本地部署到LoRA微调完整路线
  • AI写代码的完整边界:从工具选型到本地部署实践指南
  • 工具调用不能只看演示效果
  • 文献综述怎么按主题分类而不是逐篇罗列:BunnyScholar三版综述生成实测
  • Docker中轻量化部署Windows X Lite完整指南:三步跑通容器
  • 如何用 3 条命令跑通 Expo:React Native 跨端开发的快速上手指南
  • STM32WL5MOC RTC走时偏差实测:从软件排错到硬件根因分析
  • 从时间戳到任务网:WBS与关键路径驱动项目排期实战
  • 后端转大模型岗面试指南:六大核心考点与工程化思维解析
  • DeepSeek智能体开发实战:从API接入到工具调用全解析
  • UI Skills之react-native-best-practices:移动UI的Agent技能指南
  • 手写multipart上传:claude-video零依赖调用Whisper API的完整原理指南
  • GPT-SoVITS 语音克隆完整教程:从 5 秒样本到第一条合成语音
  • Android校招笔试核心考点解析:四大组件、Handler与性能优化
  • NUCLEO-H723ZG开发板入门:环境搭建、时钟配置与点灯实践
  • draw.io 桌面版教程:离线安装并导出你的第一张架构图
  • 步骤级护栏:从结果过滤到过程控制的LLM安全新范式
  • 如何运行 awesome-claude-code 资源清单:本地跑通到资源提交的实战指南
  • MemPalace知识图谱完全指南:SQLite时间实体关系图入门与实践
  • AI写代码三个月后:从效率工具到工程能力的必修课
  • 智能音乐创作不能只看演示
  • 不用微积分的PID:用Excel搭建可视化闭环控制实验台
  • XTokenChecker:验证AI网关背后的真实模型身份
  • Strix:5 分钟跑完第一次 AI 渗透测试的完整指南
  • MATLAB 2026最新版免费下载安装教程:许可证激活与报错排查
  • 校园订餐小程序毕业设计全流程:从需求到部署的实战指南
  • 安卓通知链接失效排查:从PendingIntent到URL编码实战
  • STM32 USB通信调试全攻略:从枚举失败到抓包定位