ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

自托管Durable Objects方案Celld实测:兼容Cloudflare API的分布式状态管理

自托管Durable Objects方案Celld实测:兼容Cloudflare API的分布式状态管理 在实际分布式应用开发中状态管理、数据持久化和实时协作是几个绕不开的难题。Cloudflare Durable Objects 作为一种创新的“有状态 Worker”模型为开发者提供了将状态和逻辑绑定在单一、强一致性实例上的能力极大地简化了实时应用、WebSocket 服务器、游戏会话等场景的开发。然而其强绑定于 Cloudflare 平台、无法在本地或自有服务器上运行的特性也让许多希望拥有完全控制权或需要混合云部署的团队感到掣肘。Celld 的出现正是为了解决这个痛点。它旨在提供一个与 Durable Objects API 高度兼容、但可以完全自托管的运行时环境。这意味着你可以使用熟悉的 Wrangler 开发工具链和相似的编程模型将应用部署到任何支持 Deno 或 Node.js 的环境无论是本地开发机、私有数据中心还是其他云平台。本文将带你深入实测 Celld从概念、环境搭建、核心代码实现到部署验证并与原版 Durable Objects 进行多维度对比帮助你判断在什么场景下自托管的 Celld 会成为更优的选择。1. 理解 Durable Objects 的核心模型与 Celld 的定位在深入 Celld 之前必须清晰理解 Cloudflare Durable Objects 试图解决什么问题以及它的核心抽象是什么。这决定了 Celld 的兼容性目标和设计边界。1.1 Durable Objects有状态的全局单例传统的无服务器函数如 Cloudflare Workers是无状态的。每次请求可能由不同的实例处理状态需要存储在外部服务如 KV、R2、D1中。Durable Objects 颠覆了这一模式它允许你定义一个 JavaScript 类其实例是一个全局唯一的、有状态的、长期存活的对象。全局唯一 ID每个 Durable Object 实例由一个唯一的 ID 标识。通过这个 ID来自世界任何地方的请求都能被路由到同一个实例上。强一致性同一 ID 的请求会被序列化处理避免了并发状态修改的竞态条件无需开发者手动加锁。持久化存储对象内部的状态类属性会自动被持久化到 Cloudflare 的底层存储中即使实例因闲置被回收下次唤醒时状态依然存在。WebSocket 等长连接友好它是构建多用户实时应用的理想选择因为所有连接到同一“房间”或“会话”的用户都可以通过同一个对象实例进行通信和状态同步。一个典型的 Durable Object 类看起来像这样Cloudflare Workers 语法// 定义 Durable Object 类 export class ChatRoom { constructor(state, env) { this.state state; this.env env; this.messages []; this.sessions new Set(); } async fetch(request) { // 处理 HTTP 请求例如获取消息列表 if (request.url.endsWith(/messages)) { return new Response(JSON.stringify(this.messages)); } // 处理 WebSocket 升级 if (request.headers.get(Upgrade) websocket) { const [client, server] Object.values(new WebSocketPair()); this.sessions.add(server); // ... WebSocket 事件处理逻辑 return new Response(null, { status: 101, webSocket: client }); } return new Response(Not found, { status: 404 }); } async addMessage(message) { this.messages.push(message); await this.state.storage.put(messages, this.messages); // 广播给所有连接的 WebSocket 客户端 for (const session of this.sessions) { session.send(JSON.stringify({ type: new_message, message })); } } }1.2 Celld 的使命实现 API 兼容与运行时解耦Celld 的目标不是重新发明轮子而是提供一个与 Durable Objects API 高度兼容的替代运行时。它的核心价值在于开发体验一致继续使用wranglerCLI 进行开发、测试和部署通过适配器。代码近乎零修改为 Durable Objects 编写的业务逻辑代码在 Celld 上应能直接运行或仅需极少量适配。运行时自主摆脱对 Cloudflare 平台的强依赖可以在 Deno、Node.js 等环境中运行你的“Durable Objects”应用。数据存储可配置持久化存储的后端可以替换为你选择的数据库如 SQLite、PostgreSQL、Redis等而不局限于 Cloudflare 的专有存储。简单来说Celld 试图成为 Durable Objects 规范的一个开源实现让你在享受其编程模型便利的同时拥有部署的自主权。2. 环境准备与项目初始化我们将通过一个简单的实时计数器示例来实测 Celld。这个示例包含一个 Durable Object 来维护计数器状态并通过 HTTP 接口进行增减操作。2.1 前置条件与工具链首先确保你的开发环境满足以下要求工具/环境要求说明Node.js18.x 或更高版本Celld 的 CLI 和适配器基于 Node.js。npm / yarn / pnpm最新稳定版包管理器。Wrangler CLI3.xCloudflare 的开发工具Celld 通过其进行项目管理和本地开发。Deno(可选)1.30如果你计划最终部署到 Deno 运行时需要安装。Celld 也支持 Node.js 运行时。安装 Wrangler CLInpm install -g wrangler # 或 yarn global add wrangler # 或 pnpm add -g wrangler安装后运行wrangler --version确认安装成功。2.2 创建 Celld 兼容的 Workers 项目虽然 Celld 是自托管的但它巧妙地利用了 Wrangler 的项目结构和开发流程。我们首先创建一个标准的 Cloudflare Workers 项目然后引入 Celld 适配器。创建新项目mkdir celld-counter-demo cd celld-counter-demo npm init -y安装 Wrangler 和必要的类型npm install -D wrangler npm install -D cloudflare/workers-types初始化 Wrangler 配置创建wrangler.toml文件。注意这里我们暂时不配置 Cloudflare 账户绑定因为目标是本地运行。# wrangler.toml name celld-counter-demo compatibility_date 2024-01-01 main src/index.ts compatibility_flags [nodejs_compat] [[durable_objects.bindings]] name COUNTER class_name CounterDO [durable_objects] bindings [ { name COUNTER, class_name CounterDO } ]这个配置定义了一个名为COUNTER的 Durable Object 绑定其实现类为CounterDO。2.3 引入 Celld 适配器这是使项目能在 Celld 上运行的关键步骤。Celld 提供了一个wrangler-celld适配器它劫持或模拟了 Wrangler 对 Durable Objects 的本地运行和部署命令。安装 Celld CLI 和适配器npm install -D celld wrangler-celldcelld: 核心 CLI用于启动自托管的 Celld 服务器。wrangler-celld: Wrangler 的适配器使wrangler dev和wrangler deploy命令将工作流指向本地的 Celld 服务器而非 Cloudflare。更新 package.json 脚本 为了方便我们在package.json中添加专门针对 Celld 的脚本。{ scripts: { dev:celld: wrangler-celld dev src/index.ts --local, deploy:celld: wrangler-celld deploy, start:celld: celld start } }dev:celld: 使用 Celld 适配器启动本地开发服务器。deploy:celld: 使用 Celld 适配器“部署”到本地运行的 Celld 服务器通常用于测试部署流程。start:celld: 直接启动 Celld 服务器需要先构建或提供 Worker 脚本。3. 实现 Durable Object 与 Worker 逻辑现在我们来编写业务代码。这部分代码与为原生 Cloudflare Durable Objects 编写的代码几乎完全相同这体现了 Celld 的兼容性优势。3.1 定义 Durable Object 类 (CounterDO)创建src/counter-do.ts文件// src/counter-do.ts export interface Env { COUNTER: DurableObjectNamespace; } export class CounterDO { state: DurableObjectState; storage: DurableObjectStorage; count: number; constructor(state: DurableObjectState, env: Env) { this.state state; this.storage state.storage; this.count 0; // 初始化时从持久化存储加载数据 this.state.blockConcurrencyWhile(async () { const stored await this.storage.getnumber(count); this.count stored || 0; }); } async fetch(request: Request): PromiseResponse { const url new URL(request.url); const path url.pathname; switch (request.method) { case GET: return new Response(JSON.stringify({ count: this.count }), { headers: { Content-Type: application/json }, }); case POST: const action url.searchParams.get(action); if (action increment) { this.count; } else if (action decrement) { this.count--; } else { return new Response(Invalid action, { status: 400 }); } // 将新值持久化 await this.storage.put(count, this.count); return new Response(JSON.stringify({ count: this.count }), { headers: { Content-Type: application/json }, }); default: return new Response(Method not allowed, { status: 405 }); } } }关键点解释constructor: 接收state和env。state.storage是持久化存储接口。我们使用blockConcurrencyWhile来确保在初始化加载状态时没有其他fetch请求并发执行避免状态错乱。fetch: 每个发往该 Durable Object 实例的 HTTP 请求都会调用此方法。我们根据请求方法和路径参数来增加、减少或返回计数器值。storage.put/get: 用于状态的持久化。在 Celld 中这些操作会被路由到你配置的存储后端默认为内存或 SQLite。3.2 编写主 Worker 逻辑 (index.ts)创建src/index.ts文件。这个 Worker 作为外部请求的入口负责路由到正确的 Durable Object 实例。// src/index.ts import { CounterDO } from ./counter-do; export interface Env { COUNTER: DurableObjectNamespace; } export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): PromiseResponse { const url new URL(request.url); // 假设我们通过路径 /counter/:id 来访问不同的计数器实例 const match url.pathname.match(/^\/counter\/([^\/])/); if (!match) { return new Response(Not Found. Use /counter/:id, { status: 404 }); } const counterId match[1]; // 从环境变量中获取 Durable Object 命名空间并获取特定 ID 的实例存根Stub const id env.COUNTER.idFromName(counterId); const stub env.COUNTER.get(id); // 将请求转发给 Durable Object 实例处理 return stub.fetch(request); }, }; // 必须导出 Durable Object 类Wrangler/Celld 才能识别和注册它 export { CounterDO };关键点解释idFromName(name): 根据字符串名称生成一个唯一的 Durable Object ID。相同的名称总是生成相同的 ID从而保证请求总能路由到同一个实例。get(id): 通过 ID 获取该 Durable Object 实例的“存根”Stub这是一个客户端代理。stub.fetch(request): 将接收到的 HTTP 请求原样转发给 Durable Object 实例。实际的网络调用在 Celld/Cloudflare 内部处理。3.3 配置 TypeScript创建tsconfig.json以确保类型检查正常工作特别是对于 Cloudflare/Workers 的类型定义。{ compilerOptions: { target: es2021, lib: [es2021], module: esnext, moduleResolution: bundler, types: [cloudflare/workers-types], strict: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, outDir: ./dist, rootDir: ./src }, include: [src/**/*], exclude: [node_modules, dist] }4. 在 Celld 上本地运行与验证代码编写完成后我们开始在 Celld 提供的本地环境中运行和测试我们的 Durable Objects 应用。4.1 启动 Celld 开发服务器运行之前配置好的 npm 脚本npm run dev:celld这个命令会做几件事启动一个本地的 Celld 运行时环境通常基于 Deno。启动一个本地开发服务器如localhost:8787。加载你的 Worker 和 Durable Objects 定义。提供一个与 Cloudflare Workers 预览类似的管理界面。在终端中你应该能看到类似下面的输出表明 Celld 服务器已启动⬣ Celld runtime started on http://localhost:8787 ⬣ Watching project for changes... ⬣ Worker celld-counter-demo loaded successfully. ⬣ Durable Object CounterDO registered.4.2 测试 Durable Object 的 HTTP 接口现在我们可以使用curl或任何 HTTP 客户端如 Postman来测试我们的计数器。获取计数器初始值ID 为my-countercurl http://localhost:8787/counter/my-counter预期响应{count:0}增加计数器值curl -X POST http://localhost:8787/counter/my-counter?actionincrement预期响应{count:1}再次获取确认值已更新curl http://localhost:8787/counter/my-counter预期响应{count:1}测试另一个独立的计数器实例ID 为another-countercurl http://localhost:8787/counter/another-counter curl -X POST http://localhost:8787/counter/another-counter?actionincrement curl http://localhost:8787/counter/another-counter预期行为another-counter的计数从 0 变为 1与my-counter的计数仍为 1完全独立。这验证了 Durable Object 基于 ID 的强隔离性。测试持久化重启 Celld 开发服务器CtrlC后再次运行npm run dev:celld然后再次查询my-counter。curl http://localhost:8787/counter/my-counter预期响应{count:1}。如果 Celld 配置了持久化存储如 SQLite计数应该被保留。默认的内存存储可能不会在重启后保留这取决于 Celld 的配置。4.3 验证 WebSocket 支持扩展示例Durable Objects 的强大之处在于处理 WebSocket。由于 Celld 旨在兼容 API理论上也应支持。你可以在CounterDO的fetch方法中添加 WebSocket 升级处理逻辑如 1.1 节示例然后使用 WebSocket 客户端进行连接测试。Celld 的开发服务器应能正确处理Upgrade头并维护连接。5. 部署到生产环境与配置持久化本地开发验证通过后下一步是考虑如何将基于 Celld 的应用部署到一个自托管的生产环境。5.1 构建 Worker首先需要将 TypeScript 代码构建成可在生产环境中运行的 JavaScript 包。可以使用esbuild、webpack或 Wrangler 自带的打包功能。这里我们修改package.json脚本使用 Wrangler 的构建命令由wrangler-celld适配{ scripts: { build: wrangler-celld build, deploy:prod: npm run build celld start --prod --config ./celld.config.json } }运行npm run build会在./dist或类似目录生成打包后的文件。5.2 配置 Celld 生产服务器Celld 的核心是一个可以长期运行的服务。你需要一个配置文件来定义生产环境参数。创建celld.config.json{ port: 8080, workers: [ { name: counter-app, script: ./dist/index.js, // 构建产物的入口 durableObjects: { COUNTER: CounterDO } } ], storage: { provider: sqlite, filename: ./data/celld.sqlite }, logLevel: info }配置项说明port: Celld 服务器监听的端口。workers: 定义要运行的 Worker 列表包括其名称、脚本路径和 Durable Object 绑定。storage:这是关键配置。它定义了 Durable Objects 持久化数据的后端。provider: sqlite: 使用 SQLite 数据库文件进行持久化。这是 Celld 支持的一种生产级存储方案。filename: SQLite 数据库文件路径。确保运行 Celld 的用户对该路径有读写权限。logLevel: 控制日志输出级别。Celld 可能还支持其他存储后端如 PostgreSQL、Redis 等具体需查阅其最新文档。5.3 使用进程管理器运行在生产环境中不能直接在前台运行celld start。需要使用像systemd(Linux)、pm2或docker这样的进程管理器来保证其常驻运行和自动重启。使用 PM2 示例全局安装 PM2npm install -g pm2在项目根目录创建ecosystem.config.jsmodule.exports { apps: [{ name: celld-counter-app, script: node_modules/.bin/celld, args: start --config ./celld.config.json, cwd: /path/to/your/celld-counter-demo, // 你的项目绝对路径 instances: 1, // 根据 CPU 核心数调整 autorestart: true, watch: false, max_memory_restart: 1G, env: { NODE_ENV: production } }] };启动应用pm2 start ecosystem.config.js设置开机自启pm2 startup然后按照提示操作最后pm2 save。5.4 配置反向代理与域名在生产环境你通常会在 Celld 服务器前放置一个反向代理如 Nginx、Caddy来处理 TLS 终止、静态文件、负载均衡等。Nginx 简单配置示例(/etc/nginx/sites-available/celld-app)server { listen 80; server_name your-domain.com; # 你的域名 location / { proxy_pass http://localhost:8080; # 指向 Celld 服务端口 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; # 支持 WebSocket proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }配置后重启 Nginx 并通过域名访问你的应用。6. Celld 与 Cloudflare Durable Objects 深度对比选择 Celld 还是原生 Cloudflare Durable Objects取决于你的具体需求。下表从多个维度进行对比对比维度Cloudflare Durable ObjectsCelld (自托管)核心价值全球分布式、低延迟、无缝集成的有状态计算。Durable Objects 编程模型的自托管实现部署自主。部署位置仅限 Cloudflare 全球网络。任意支持 Deno/Node.js 的环境自有服务器、私有云、其他公有云。数据持久化Cloudflare 内部实现的分布式存储自动处理。可配置SQLite, PostgreSQL, Redis 等需自行维护数据库。全球分布与延迟自动在全球边缘节点运行用户访问延迟低。取决于你部署服务器的地理位置和网络架构需自行实现多区域部署和负载均衡。扩展性由 Cloudflare 自动管理理论上无限扩展。需要自行设计扩展方案如水平扩展 Celld 实例、数据库分片。运维复杂度极低Cloudflare 负责底层基础设施、安全、扩缩容。高需要自行维护服务器、运行时、数据库、监控、备份、安全更新等。成本模型按请求次数、Durable Object 调用次数和存储量计费。主要是服务器/虚拟机成本、数据库成本和自身运维人力成本。开发体验使用 Wrangler CLI与 Workers 生态无缝集成。高度兼容可使用相同工具链Wrangler和代码但需额外配置 Celld 运行时。功能完整性完整支持 Durable Objects 规范包括 WebSocket、Alarms、Transactional Storage API。高度兼容但可能落后于官方最新特性需关注项目更新。WebSocket等核心功能通常已实现。厂商锁定高代码和架构深度绑定 Cloudflare。低代码可移植运行时可控。适用场景1. 面向全球用户的实时应用。2. 希望极致简化运维专注业务逻辑。3. 项目已在 Cloudflare Workers 生态内。1. 数据主权要求高必须部署在特定区域或私有环境。2. 已有成熟的自有机房或云架构希望引入 Durable Objects 模型。3. 作为混合云架构的一部分部分逻辑需在本地运行。4. 对 Cloudflare 服务有访问限制或成本考量。7. 常见问题排查与最佳实践在开发和部署 Celld 应用过程中你可能会遇到以下典型问题。7.1 常见问题排查表问题现象可能原因检查与解决步骤wrangler-celld dev启动失败提示找不到模块或命令。1.celld或wrangler-celld未正确安装。2. Node.js 版本过低。3. 项目路径包含特殊字符或空格。1. 运行npm list celld wrangler-celld检查安装。2. 确认 Node.js 18。3. 尝试在纯英文路径下创建项目。访问localhost:8787返回404或Worker not found。1. Worker 脚本未正确加载或编译错误。2.wrangler.toml配置错误如main路径不对。3. Celld 服务器未成功启动 Worker。1. 查看终端启动日志确认 Worker 加载成功。2. 检查wrangler.toml中main字段指向的入口文件是否存在且无语法错误。3. 尝试一个最简单的无 Durable Object 的 Worker 脚本进行测试。Durable Object 状态在重启后丢失。1. 开发模式下 Celld 默认使用内存存储。2. 生产配置中storage未正确配置或路径无权限。1. 开发时状态丢失是预期行为便于调试。2. 生产环境检查celld.config.json中的storage配置并确保进程对数据库文件有读写权限。WebSocket 连接失败或无法维持。1. 反向代理如 Nginx未正确配置 WebSocket 代理。2. Celld 运行时对 WebSocket 的支持存在 bug 或版本问题。1. 检查反向代理配置确保包含Upgrade和Connection头见 5.4 节。2. 直接连接 Celld 服务器端口绕过代理测试 WebSocket。3. 查阅 Celld 项目 Issue确认 WebSocket 支持状态。性能不佳响应延迟高。1. 自托管服务器配置低或网络差。2. 存储后端如 SQLite成为瓶颈特别是在高并发写场景。3. Durable Object 内部逻辑复杂阻塞了请求队列。1. 监控服务器资源CPU、内存、磁盘 IO。2. 考虑将存储后端更换为性能更高的数据库如 PostgreSQL、Redis。3. 优化 Durable Object 内部逻辑避免长时间同步操作善用异步。7.2 自托管环境下的最佳实践存储后端选型开发/测试使用 SQLite简单易用。小型生产PostgreSQL 是可靠的选择提供了良好的并发性能和可靠性。高性能、低延迟场景考虑 Redis 作为存储后端如果 Celld 支持但需注意 Redis 的持久化策略和数据丢失风险。务必启用定期备份无论选择哪种数据库都必须建立备份机制。高可用与扩展Celld 实例本身可以水平扩展但 Durable Object 的状态是分片的。你需要一个策略如一致性哈希将特定的对象 ID 路由到特定的 Celld 实例。这通常需要在前端负载均衡器或 Celld 客户端层面实现Celld 项目本身可能尚未提供开箱即用的集群方案。数据库也需要做相应的高可用配置如 PostgreSQL 流复制、Redis Sentinel/Cluster。监控与日志配置 Celld 的logLevel为info或debug并将日志收集到集中式系统如 ELK、Loki。为你的应用和 Durable Objects 添加业务指标如请求量、错误率、对象活跃数并使用 Prometheus 等工具进行监控。监控数据库连接数、查询延迟和存储空间。安全使用反向代理如 Nginx处理 TLS/SSL 加密。限制 Celld 服务器的防火墙端口仅允许来自反向代理或内部网络的访问。定期更新 Celld、Node.js/Deno 以及数据库的版本修复安全漏洞。如果 Durable Objects 处理敏感逻辑应在对象内部实现额外的认证和授权检查不要完全依赖边缘 Worker。开发流程利用wrangler-celld dev进行本地开发和热重载。建立与生产环境相似的 staging 环境使用相同的存储后端进行集成测试。由于 Celld 可能与 Cloudflare 官方实现有细微差别务必针对核心的持久化和 WebSocket 功能编写自动化测试。Celld 为开发者提供了一个宝贵的选择使得 Durable Objects 强大的有状态编程模型不再局限于单一的云平台。它特别适合那些对数据位置、成本控制或技术栈自主性有严格要求的团队。然而选择 Celld 也意味着你需要承担起整个运行时和数据层的运维责任。在决定之前请务必根据 6.1 节的对比表仔细权衡“开发的便利性与模型的先进性”与“运维的复杂性与基础设施的自主权”之间的利弊。对于大多数初创项目或面向全球的应用Cloudflare 的原生服务可能是更优解而对于有特定部署约束的企业级应用或混合云场景Celld 则打开了一扇新的大门。
返回列表