前言

在传统的 Web 应用中,我们通常会把计算和状态分开:应用服务尽量保持无状态,数据放进 MySQL、Redis 或消息队列。当业务需要聊天室、协同编辑、多人游戏或 AI Agent 时,还要继续处理连接路由、并发控制、分片和故障恢复。

Cloudflare Durable Objects 提供了一个很有意思的抽象:每个对象都有稳定的 ID、单线程执行环境和私有存储。可以把一个用户、一个文档或一个聊天室放进独立对象中,让状态和处理状态的代码待在一起。

但 Durable Objects 一直与 Cloudflare 的基础设施绑定。celld 则尝试把这套编程模型带到我们自己的服务器和对象存储中。

用官方的描述来说:

Self-hosted, distributed Durable Objects.

什么是 Cell

在 celld 中,一个 cell 就相当于一个 Durable Object:它是一个有名称的小型服务,拥有独立的 SQLite 数据库,可以处理 HTTP 请求、维护 WebSocket 连接、设置 Alarm,也可以主动访问外部服务。

一个 Cell 很适合对应一个天然的业务边界,例如:

  • 一个聊天室或协作文档;
  • 一个用户、租户或设备;
  • 一个游戏房间;
  • 一个拥有记忆、Inbox 和调度任务的 AI Agent。

每个 Cell 在一个线程中执行。只有当前请求发生 await 时,其他请求才可能交错执行,而存储操作是同步且不会交错的。这意味着许多原本需要分布式锁或事务协调的问题,可以收敛到 Cell 内部处理。

Cell 也不需要一直驻留在内存中。它可以从 Resident 变为 Hibernated 或 Inactive;Inactive Cell 只保留对象存储中的数据,直到下一次请求到来时再恢复。这让大量低频活跃的 Agent、房间或租户不会持续占用计算资源。

celld 是如何工作的

celld 的底层可以概括为:

celld = V8 + SQLite + LTX + Object Storage

每台运行 celld 的机器是一个 Node,使用同一个 Bucket 的多个 Node 组成一个 Fleet。celld 没有额外的成员服务、故障检测器或共识集群,而是直接把对象存储当作协调者。

Cell 的所有权记录保存在 Bucket 中,并通过对象存储提供的条件写入来竞争。任何时刻只有一个 Node 能持有某个 Cell;所有权是有期限的 Lease,Node 必须持续续约。节点宕机后 Lease 会过期,其他 Node 就可以接管这个 Cell。

每个 Cell 的状态存放在 SQLite 中,再通过 LTX 增量复制到 Bucket。对于单节点部署,一次写入需要先持久化到 Bucket;当 Fleet 中有两个或更多节点时,主节点可以先把写入复制到另一个节点的磁盘,再异步上传到 Bucket,从而减少同步写入的延迟。

官方给出的核心承诺是 RPO=0:已经确认成功的写入,在节点故障后不会丢失。当然,这并不等于“自托管一定更可靠”。你仍然需要为服务器、网络、Bucket、容量和监控负责,只是故障域和运行证据都回到了自己能够观察与控制的基础设施中。

Cloudflare Workers 兼容性

celld 会读取现有的 wrangler.jsonwrangler.jsonc,因此很多 Cloudflare Workers 应用不需要修改代码就可以部署。当前支持的主要能力包括:

  • Workers 和 Durable Objects;
  • KV、Queues、D1 和 R2;
  • Workflows 和 Cron Triggers;
  • 静态资源和 WebAssembly;
  • Service Bindings。

Durable Object Facets 和 Dynamic Workers 仍属于实验性功能。Workers AI、Vectorize、Hyperdrive、Browser Rendering、Email Workers 和 Python Workers 等依赖 Cloudflare 特定基础设施的能力暂不支持。

这里还有一个我很喜欢的设计:遇到不认识的 Wrangler 配置时,celld deploy 会直接报错,而不是静默忽略。这能避免应用看起来部署成功,实际却缺少关键能力。

安装和本地开发

celld 提供了一个静态可执行文件,可以通过官方脚本安装:

curl -fsSL https://celld.dev/install.sh | sh

也可以直接运行容器镜像:

docker run --rm ghcr.io/denoland/celld --version

如果已经有一个 Wrangler 项目,进入项目目录后执行:

celld dev

它会启动本地对象存储、部署应用并运行一个 Node,默认监听 http://127.0.0.1:9876。整个过程不需要 Docker,也不需要准备云端 Bucket。

本地状态会保存在项目的 .celld/dev 目录,所以应该先把 .celld/ 加入 .gitignore。如果需要清空之前的状态重新开始,可以执行:

celld dev --clean

celld dev 还会监控源码和配置变化。构建失败时旧版本会继续运行,构建成功后才会重启,同时保留已有的持久化状态。这一点对于调试 Durable Object 很实用。

部署一个 Fleet

生产环境需要先准备 S3 兼容存储、Google Cloud Storage 或 Azure Blob Storage。以 S3 为例,可以先部署 Wrangler 项目:

export CELLD_BUCKET=s3://my-cells-bucket

celld deploy . --bucket "$CELLD_BUCKET"

然后启动第一个 Node:

celld \
  --bucket "$CELLD_BUCKET" \
  --listen 0.0.0.0:8080 \
  --internal-listen 10.0.0.12:8081 \
  --advertise 10.0.0.12:8081

增加节点时,只需要让新 Node 使用相同的 Bucket,并为内部监听地址配置一个其他节点可以访问的地址。Node 会通过 Bucket 中的 Lease 自动发现彼此,不需要执行 Join 命令,也不需要维护固定成员列表。

8080 可以暴露给负载均衡器,但内部监听端口只能放在可信私有网络中。celld 自身不终止 TLS,官方建议通过入口代理处理 TLS,并使用 WireGuard 或 Tailscale 等方式保护跨节点通信。

适合什么场景

celld 最吸引我的地方,并不是“把 Cloudflare 搬回自己的机房”,而是让 Durable Objects 这种有状态 Actor 模型成为一个可以独立选择的基础设施组件。

如果系统能够自然地按用户、文档、房间、设备或 Agent 分片,同时又需要 WebSocket、持久状态和单线程一致性,那么 celld 很值得尝试。已有 Workers 项目、需要数据自主权、希望控制故障域,或者在大量低频 Cell 下关注成本时,它也会很有吸引力。

但它并不是传统数据库和 Kubernetes 的直接替代品。跨 Cell 的查询与事务会变得更复杂;生产环境仍然需要负载均衡、TLS、私有网络、Bucket 权限、监控和容量管理。Wrangler 兼容也不是 100%,部署之前应该仔细阅读兼容性列表限制说明

总结

Durable Objects 把“状态放在哪里、请求由谁处理、并发如何控制”收敛进一个很简洁的编程模型,而 celld 进一步把这套模型从单一云平台中解耦出来。

它用对象存储解决协调和长期状态,用 SQLite 管理每个 Cell 的数据,再通过 LTX 和节点间复制实现持久化与故障接管。这个架构并没有消除分布式系统的复杂度,但把复杂度封装在一个可观察、可自托管的运行时里。

目前 celld 仍是一个很新的项目,兼容性和生产验证都需要继续观察。不过对于实时协作、多人房间、大规模 AI Agent 以及天然按租户分片的应用,它提供了一条非常值得关注的新路径。

I hope this is helpful, Happy hacking…