Cloudflare Durable Objects架构深潜:Company Brain如何扛住高并发Slack事件流
【免费下载链接】company-brainOpen-sourcing our company brain - A teammate in your Slack that remembers everything your team says, and can go do the work.项目地址: https://gitcode.com/gh_mirrors/co/company-brain
Company Brain 是一个驻留在 Slack 里的"团队大脑":它记住团队在频道里说过的每一件事,能回答、能检索、还能直接动手干活(开 Issue、读代码、跑脚本)。而支撑这条 Slack 事件流高速运转的核心,正是 Cloudflare Durable Objects(DO)——本文带你深潜这套架构,看懂它是如何用"按组织隔离的单实例 + KV 幂等去重 + Fiber 检查点恢复"扛住高并发事件洪峰的。
一、为什么 Slack 事件流是典型的高并发场景
Slack 的事件订阅有几个"天然高并发"特性:
- 重试是常态:Slack 收到非 2xx 响应就会重发同一事件,
event_id不变; - 同一消息会以多种事件类型竞速到达(mention、message、thread 回复几乎同时推来);
- 事件是"长任务"的触发器:一条 @ 消息背后可能是几十步的工具调用循环,处理远超普通 HTTP 请求的毫秒级预算。
如果直接把每条事件丢给无状态 Worker 处理,轻则重复回答,重则两个 Worker 同时改同一频道状态。Company Brain 的解法是三层防线:路由层去重 → DO 单实例串行 → Fiber 幂等恢复。完整系统地图见 docs/architecture.md。
二、DO 架构总览:每个组织一个"专属大脑实例"
在 wrangler.jsonc 中注册了两个 Durable Object 绑定:
"durable_objects": { "bindings": [ { "name": "COMPANY_BRAIN_AGENT", "class_name": "CompanyBrainAgent" }, { "name": "Sandbox", "class_name": "Sandbox" } ] }- CompanyBrainAgent:每个组织(orgId)一个实例,通过
getAgentByName(COMPANY_BRAIN_AGENT, orgId)路由。所有 Slack 回合、审批卡、定时任务、自动研究、试用提醒都收敛在这一个单例里——同组织的并发事件被天然串行化,跨组织之间又完全隔离; - Sandbox:代码沙箱的运行时实例。
DO 类刻意保持"薄壳":src/brain/turn/agent.ts 只声明方法与生命周期,重逻辑在首次调用时才import("./agent.impl")懒加载。这是为了控制 Worker 全局启动体积,避开 Cloudflare 的校验预算限制。
入口端点在 src/routes/slack/index.ts:Slack 把带签名的 POST 打到这里,Worker 先验 HMAC、再分类、立即回{ok:true},真正的重活交给c.executionCtx.waitUntil(...)在 DO 里异步执行——响应快、处理稳,二者解耦。
三、第一层防线:KV 幂等去重与回合锁
在事件进入 DO 之前,src/routes/slack/index.ts 用BRAIN_KV做了两级去重:
- 事件级幂等键
slack:evt:{event_id}——Slack 的重发事件在这里被直接吞掉(TTL 一天); - 回合锁
slack:turn:{teamId}:{channel}:{ts}——同一用户消息无论以几种事件类型竞速到达,只有一个能进入回合,其余看到锁即返回成功。
注意去重键只在任务到达终态后才写入(如onSlackTeamJoin返回terminal=true才落键),这样瞬时失败时 Slack 的重试还能补投递——幂等与可重试之间的取舍非常讲究。
四、第二层防线:Fiber 检查点与崩溃恢复
显式的 Slack 回合不是普通方法调用,而是跑在一个Fiber上(src/brain/turn/agent.ts 中的startFiber):
- 幂等键:
slack:{teamId}:{eventId}(见 src/brain/turn/slack-turn-fiber.ts),重复投递被startFiber直接拒绝; - 快照检查点:Fiber 运行中持续
fiber.stash(snapshot)写入阶段快照,字段包括phase(accepted → running → progress → answered → completed)、threadKey、turnId、progressMessageTs、terminalProposal等; - 崩溃恢复:如果 DO 实例在部署、重启中被重置,
onFiberRecovered会读取最后一个快照——已回复则直接标记完成;卡在"有终稿待发送"则补发;否则带着recoveredTurn上下文再尝试一次(MAX_SLACK_TURN_RECOVERY_ATTEMPTS = 1),超过上限则安全收尾并上报。
这套机制让"部署瞬间正好卡在长回合中间"这种最尴尬的时序问题变得可自愈。
五、第三层防线:DO SQL 本地存储与重置容错
DO 自带 SQLite(DO SQL),Company Brain 把回合记录、审批状态、chime 预算、rollout 游标、团队邀请进度等高频写、按组织隔离的状态全部放在 DO 本地表里(约定brain_*前缀,清单见 docs/agent.md),避免每次回合都打远端数据库。
平台级重置(代码更新导致的 DO reset、内存超限 OOM)在 src/brain/tools/mcp/errors.ts 被精细区分:
| 重置类型 | 判定 | 策略 |
|---|---|---|
| 代码更新重置 | isDurableObjectCodeUpdateReset | 视为平台方原因,原样重试 |
| 存储内部错误重置 | 正则匹配 reset 报错 | 同上,可重试 |
| 内存超限 OOM | isDurableObjectMemoryLimitReset | 禁止原样重试(必然再 OOM),提示缩小单次数据量 |
把"平台重置"和"程序写爆内存"区分开再决定重试策略,是高并发长任务运行时非常实用的一课。
六、定时任务:DO Alarm 让大脑"睡了也醒"
DO 的 schedule/alarm 能力承担了大量延迟与定时工作:自动研究的即时入队(schedule(0, ...)让 HTTP 立即返回)、审批卡片到期自动收起、团队邀请逐人 DM(每个 tick 发一条,天然限速)、频道观察循环、turn 结束后的延迟反思等。所有回调都定义在 src/brain/turn/agent.ts 的薄壳上,runAutoResearch甚至会主动拒绝已退役的 cron 型调度并取消它——防御性编程贯穿始终。
七、新手上手:本地开发与部署要点
- 本地开发只需三步(详见 README.md 的 Local development 一节):
bun install→ 复制.dev.vars填入两个 API Key →bun run dev; - 免费计划即可跑通全部核心功能;开启 Workers Paid 后,wrangler.jsonc 里注释掉的 containers 块可以解锁内置容器沙箱(shell、git、Python);
- 迁移由 scripts/bundle-migrations.ts 打包进 Worker,首个请求自动应用,无需独立数据库服务。
八、小结:三层防线各司其职
| 层级 | 机制 | 解决的问题 |
|---|---|---|
| Worker 路由层 | HMAC 验签 +BRAIN_KV幂等键 + 回合锁 | Slack 重发、多事件类型竞速 |
| DO 实例层 | 每组织单例串行 +waitUntil异步化 | 同组织写冲突、响应超时 |
| Fiber 层 | 幂等键 + 快照检查点 + 一次恢复尝试 | 部署/崩溃打断长回合 |
配合 DO SQL 本地状态与精细的重置分类,Company Brain 用纯无服务器组件就构建出了一个"事件不丢、回合不断、状态自愈"的高并发 Slack Agent 运行时。想继续深挖,推荐按 docs/architecture.md → docs/agent.md → docs/slack.md 的顺序阅读,源码锚点均已标注。
【免费下载链接】company-brainOpen-sourcing our company brain - A teammate in your Slack that remembers everything your team says, and can go do the work.项目地址: https://gitcode.com/gh_mirrors/co/company-brain
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考