ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Cloudflare Workers不是操作系统,而是边缘运行时抽象

Cloudflare Workers不是操作系统,而是边缘运行时抽象

1. “Cloudflare-OS”不是操作系统,而是开发者对Workers运行时的一种戏称式表达

最近在技术社区和GitHub讨论区里,“cloudflare-os”这个词频繁出现,但几乎没人能说清它到底指什么——它既不在Linux发行版列表里,也不见于任何操作系统认证目录,更没有ISO镜像可供下载。我第一次看到这个词是在一个React Server Components的部署帖子里,作者写道:“终于不用搭Node.js后端了,直接跑在cloudflare-os上,冷启动归零。”当时我就愣住了:Cloudflare明明只提供边缘计算服务,怎么突然“造出操作系统”了?

其实,“cloudflare-os”根本不是真正的操作系统(Operating System),而是一种开发者圈内自发形成的、带有调侃与隐喻色彩的技术术语。它的核心指向非常明确:Cloudflare Workers 提供的无服务器(serverless)JavaScript/TypeScript 运行时环境,因其高度抽象、开箱即用、无需管理进程与系统资源的特性,被类比为一种轻量级、分布式的“边缘操作系统”。

这个说法之所以能流行起来,是因为它精准击中了现代前端与全栈开发者的真实体验痛点。过去我们部署一个Web应用,至少要面对三层抽象:

  • 最底层是物理机或虚拟机(Linux/macOS/Windows);
  • 中间层是运行时环境(Node.js + npm + 依赖树 + process管理);
  • 最上层才是业务代码(Express/Fastify/Next.js等框架)。

而Workers把中间层和底层彻底折叠了:你提交一段export default { fetch }函数,Cloudflare就自动为你分配一个隔离的V8实例,预置好fetch、Request、Response、Headers、Crypto等Web标准API,甚至内置了Durable Objects、R2、KV等状态服务接口。你不再需要npm install express,不需要写app.listen(3000),不关心process.pid,甚至看不到/proc或/dev——整个环境干净得像一块刚格式化的SSD。

提示:“cloudflare-os”中的“os”,不是指传统意义上的操作系统内核(kernel)、进程调度器(scheduler)或文件系统(VFS),而是指一套完整、自洽、可编程的边缘执行契约(execution contract)。它定义了“代码如何被加载”“请求如何被路由”“状态如何被持久化”“错误如何被暴露”——这些恰恰是传统OS对应用程序提供的核心契约。

这也能解释为什么搜索“cloudflare-os”会带出一堆看似无关的热词:小米澎湃OS、飞牛OS、OpenHarmony、Cachy OS……它们共同的关键词是“OS”,但语境完全不同。前者是消费级终端操作系统,后者是开发者对云原生运行时抽象层级的一次集体命名尝试。就像当年大家把Docker容器称为“轻量级虚拟机”一样,“cloudflare-os”不是技术误用,而是一种认知迁移——当基础设施抽象到足够高,用户感知的边界就开始模糊。

我试过用Workers跑一个纯静态站点生成器(类似Hugo但用JS实现),整个构建流程完全在边缘完成:从GitHub Webhook触发,拉取Markdown源文件,解析渲染成HTML,再存入R2,最后通过Pages或自定义域名对外提供服务。全程没有SSH、没有systemd、没有nginx.conf,甚至连ps aux都不存在。这种“无感运维”的体验,确实让人产生一种错觉:仿佛自己不是在调用API,而是在一个全新的OS上编写原生程序。

所以,如果你在文档、PR描述或Slack频道里看到“cloudflare-os”,请先别急着去GitHub搜仓库——它大概率不是某个开源项目名,而是某位工程师在描述“我在Workers上实现了XX功能”时,顺手打的一个生动比喻。理解这一点,才能避免后续所有技术选型和架构设计上的根本性偏差。

2. Workers运行时的本质:V8 isolate + Web Standard API + Cloudflare扩展层

要真正吃透“cloudflare-os”背后的技术实质,必须拆解Workers运行时的三层结构。这不是黑盒,Cloudflare官方文档已公开其核心设计原则,而实际开发中踩过的每一个坑,都在反向验证这套架构的边界。

2.1 第一层:V8 Isolate —— 隔离但非独立的操作系统进程

Workers底层运行在Google V8引擎之上,但绝不是每个Worker对应一个独立的OS进程。相反,Cloudflare采用的是V8的Isolate机制:一个物理Worker实例(即你配置的某条路由规则)会被映射到一个共享的V8 Isolate池中,由Cloudflare的调度器动态分配。这意味着:

  • 同一Isolate内可并发执行多个请求(request-level concurrency),但不支持多线程(no threads);
  • 每个Isolate有独立的堆内存(heap),但没有独立的文件系统、网络栈或设备驱动;
  • setTimeout、setInterval、Promise.then等异步操作由V8事件循环驱动,而非OS内核的epoll/kqueue;
  • process对象根本不存在——你无法获取process.env.NODE_ENV,也不能调用process.exit()。

我曾试图在Workers里模拟一个简单的“后台任务队列”,用setTimeout(fn, 30000)延迟执行清理逻辑,结果发现:只要该Isolate在30秒内没有新请求进入,Cloudflare会主动销毁该Isolate,导致定时器永远无法触发。后来查文档才确认:Workers的Isolate生命周期完全由请求驱动,空闲超时默认为5秒(可配置为最长30秒),且不可延长。这与Linux进程的systemd守护模式或Kubernetes的Pod保活机制有本质区别——它不是“进程”,而是“请求处理器”。

注意:所谓“cold start”在Workers中并不存在传统意义的冷启动(如JVM加载类、Python import模块)。V8 Isolate的初始化耗时极短(毫秒级),真正的延迟来自DNS解析、TLS握手和边缘节点缓存未命中。因此,Workers的“冷启动”其实是网络层问题,而非运行时问题。

2.2 第二层:Web Standard API —— 浏览器能力的逆向移植

Workers暴露的全局API,90%以上直接来自Web平台标准(WHATWG/W3C),但关键在于:这些API被从浏览器环境中剥离,重新注入到服务端执行上下文。例如:

  • fetch():不再是浏览器发起的跨域请求,而是Workers内部的HTTP客户端,支持POST/PUT/DELETE,且默认启用keep-alive连接池;
  • Request/Response:构造函数与浏览器一致,但Response.body可接受ReadableStream、Uint8Array、string,甚至FormData(需手动序列化);
  • Crypto.subtle:提供完整的Web Crypto API,包括encrypt/decrypt/sign/verify,且密钥可安全存储在secret环境变量中;
  • TextEncoder/TextDecoder:UTF-8编码/解码原生支持,无需引入第三方库。

但也有重要差异:

  • 没有window、document、localStorage、indexedDB——这些是浏览器专属;
  • navigator对象仅包含userAgent和platform两个只读字段,其余全部缺失;
  • URL和URLSearchParams完全可用,但location对象不存在(因为无全局上下文)。

我曾用Workers实现一个JWT校验中间件,核心逻辑是:

const key = await crypto.subtle.importKey( 'jwk', { /* 公钥JWK */ }, { name: 'RS256' }, false, ['verify'] ); await crypto.subtle.verify('RS256', key, signature, data);

这段代码在Chrome DevTools里能跑,在Workers里也能跑,且性能相当——因为V8对Web Crypto的实现是同一套C++后端。这就是“Web Standard API”带来的最大红利:开发者无需学习新语法,只需理解运行时约束。

2.3 第三层:Cloudflare扩展层 —— 真正定义“cloudflare-os”特性的部分

如果说前两层是“通用能力”,那么第三层才是Cloudflare赋予Workers独特气质的关键。它包含四类核心扩展:

扩展类型典型API作用是否可替代
状态服务KV.get(),DurableObjectStub.get()提供键值存储与强一致性对象否(无同类FaaS服务提供)
对象存储R2.put(),R2.get()无元数据开销的对象存储,支持流式上传否(S3兼容但无边缘缓存)
网络代理event.passThroughOnException()将失败请求透传至上游源站是(但需自行实现重试逻辑)
安全控制cf.country,cf.asn,cf.botManagement基于边缘的地理/IP/机器人识别否(需额外CDN或WAF集成)

其中,Durable Objects最值得深挖。它不是一个数据库,而是一个分布式Actor模型的运行时:每个DO实例有唯一ID,可跨请求保持内存状态,支持fetch()方法处理HTTP请求,并通过stubs实现跨DO通信。我用它实现了一个实时计数器服务,代码仅30行:

export class Counter { constructor(state, env) { this.state = state; this.env = env; } async fetch(request) { const { pathname } = new URL(request.url); if (pathname === '/inc') { const count = (await this.state.storage.get('count')) || 0; await this.state.storage.put('count', count + 1); return new Response(String(count + 1)); } return new Response('OK'); } }

这个Counter类在Workers中注册后,每个/counter/<id>路径都会绑定一个独立DO实例。关键点在于:DO的生命周期由Cloudflare托管,你无需关心实例启停、故障转移或水平扩展——它就是“操作系统”里的“进程”。当你调用stub.fetch()时,Cloudflare自动将请求路由到对应DO所在的边缘节点,如果该节点宕机,则自动在另一节点重建实例并恢复状态(基于storage持久化)。

这才是“cloudflare-os”最接近传统OS的地方:它提供了进程抽象(Durable Object)、文件系统抽象(KV/R2)、网络协议栈(fetch + cf.* metadata)和安全沙箱(Isolate)。只不过,这一切都发生在毫秒级延迟的全球边缘网络上,而非单台服务器。

3. 为什么不能把Workers当作真实OS来用?五个硬性边界与实测案例

尽管“cloudflare-os”的比喻很形象,但若真按操作系统思维去设计应用,很快就会撞上一堵看不见的墙。我经历过三次典型翻车现场,每次都是因为忽略了Workers的底层约束。下面用真实案例说明五个不可逾越的硬性边界。

3.1 边界一:无本地文件系统 —— 所有“文件操作”必须转为内存或R2

Workers环境里没有fs模块,也没有/tmp目录。你不能fs.writeFileSync('config.json', JSON.stringify(cfg)),也不能require('path')。所有文件级操作必须显式转换为:

  • 小文件(<1MB):用Uint8Array或string存入内存,或存入KV(最大值10MB,但key-value结构不适合大文本);
  • 大文件(>1MB):必须用R2.put()上传,再通过R2.get()读取;
  • 临时缓存:用Cache API(仅限Pages项目)或KV(带TTL)模拟。

我曾尝试用Workers部署一个Markdown转PDF服务,依赖pdfmake库。该库内部会动态生成字体文件并写入临时目录。结果部署后报错:Error: ENOENT: no such file or directory, open '/tmp/font.ttf'。排查发现,pdfmake的vfs(virtual file system)模块默认使用Node.js的fs,而Workers根本没有fs。

解决方案不是“找替代库”,而是重构数据流:

  1. 前端上传Markdown和字体文件(Base64编码);
  2. Workers接收后,将字体解码为Uint8Array,传给pdfmake的vfs接口;
  3. PDF生成后,直接返回Response,不落地存储。

实测心得:Workers里“文件”概念必须消失,代之以“字节流”和“对象引用”。任何依赖fs的NPM包(如sharp图像处理、xlsx表格解析)都需确认其浏览器兼容版本,或改用Cloudflare官方支持的@cloudflare/kv-asset-handler等工具。

3.2 边界二:无长期运行进程 —— 所有后台任务必须转为事件驱动

Workers的执行模型是“请求-响应”,单次执行最长30分钟(Pro计划),但绝大多数场景下,超1秒的同步阻塞操作就会触发CPU限制警告。你不能写一个while(true)轮询数据库,也不能用setInterval维持心跳。

我曾为IoT设备做消息聚合服务,需求是每5秒从MQTT Broker拉一次数据。最初代码如下:

// ❌ 错误示范:Workers里无法长期运行 let intervalId; addEventListener('install', () => { intervalId = setInterval(pollMQTT, 5000); });

结果部署后,install事件根本不会触发(Workers无生命周期事件),setInterval也因Isolate销毁而失效。

正确做法是改为事件驱动+状态存储:

  • 设备上报数据时,触发Workers写入KV或Durable Object;
  • 另一个定时触发的Workers(通过Cloudflare Cron Triggers)每5秒读取一次状态,聚合后推送到下游;
  • 或直接用Durable Object的alarm()方法实现精确定时(精度±1秒)。

注意:Cron Triggers是独立于Workers的调度服务,每月免费10万次,适合周期性任务。而DO的alarm()需在DO实例内设置,适合实例级定时(如聊天室自动踢人)。

3.3 边界三:无TCP/UDP Socket —— 所有网络通信必须走HTTP(S)

Workers只暴露fetch()API,不支持net.Socket、dgram或WebSocket服务端。你无法搭建SMTP服务器、Redis代理或自定义TCP协议网关。

我曾想用Workers做邮件转发服务,接收POST /webhook后,用nodemailer发邮件。但nodemailer底层依赖net.Socket连接SMTP服务器,而Workers不提供该能力。

解决方案是:

  • 改用SMTP服务商的HTTP API(如SendGrid、Mailgun);
  • 或将邮件发送逻辑下沉到自有服务器,Workers只做鉴权和参数组装,再fetch()调用后端。

关键认知:Workers不是“精简版Linux”,而是“HTTP优先的边缘执行环境”。任何非HTTP协议的适配,都必须由上游服务完成,Workers只负责编排与转换。

3.4 边界四:无eval()与动态代码加载 —— 安全沙箱的代价

为防止恶意代码执行,Workers禁用eval()、Function()构造器、import()动态导入(仅支持顶层import)及WebAssembly.instantiateStreaming()。这意味着:

  • 无法实现插件系统(如动态加载用户JS脚本);
  • 无法用acorn/esprima解析并执行JS字符串;
  • JSONP回调模式完全不可用。

我曾开发一个低代码表单引擎,允许用户输入JS表达式作为校验规则(如value > 0 && value < 100)。最初用eval()实现,结果在Workers里直接报错ReferenceError: eval is not defined。

最终方案是:

  • 用@babel/parser解析AST,白名单检查节点类型(只允许BinaryExpression、LogicalExpression等安全节点);
  • 用@babel/generator生成安全函数体;
  • 用new Function()构造(但Workers禁用!)→ 改为预编译模板:return (${expr});,再用Function构造(仍被禁)→ 最终采用tiny-eval库,它用Function构造但被Workers拦截 → 彻底放弃,改用固定规则集(大于、小于、等于、正则匹配)。

教训:Workers的安全模型是“默认拒绝”,而非“默认允许”。任何涉及代码动态执行的需求,都需提前规划替代方案,切勿寄希望于“绕过沙箱”。

3.5 边界五:无process与系统级环境 —— 所有环境变量必须显式注入

Workers不提供process对象,process.env为空对象。你不能用dotenv加载.env文件,也不能用os.platform()判断系统类型。

我曾迁移一个Node.js CLI工具到Workers,其中一行代码是:

if (os.platform() === 'win32') { pathSep = '\\'; } else { pathSep = '/'; }

在Workers里,os模块不存在,os.platform()直接报错。

解决方案很简单:

  • 所有环境变量通过Cloudflare Dashboard或wrangler.toml的vars字段注入;
  • 路径分隔符等常量,直接硬编码为'/'(Workers只运行在Linux-like环境,且URL路径规范强制/);
  • 系统信息需求,用navigator.userAgent(仅限浏览器上下文)或cf.*元数据替代。

经验:Workers的“环境”是扁平的、声明式的。与其猜测运行时细节,不如把所有外部依赖(数据库地址、API密钥、特征开关)都作为env变量注入,并在代码中显式使用env.DB_URL而非process.env.DB_URL。

4. 从“cloudflare-os”到真实项目:一个可复用的边缘博客系统架构

理解了“cloudflare-os”的本质与边界,下一步就是把它变成生产力。我用Workers+R2+KV搭建了一个纯边缘博客系统( blog.edge ),它不依赖任何后端服务器,所有内容生成、存储、缓存、评论都跑在Cloudflare上。下面拆解其核心模块,展示如何将理论转化为可落地的架构。

4.1 内容存储层:R2 + KV 的协同设计

传统博客用MySQL存文章,用Redis缓存热点。在Workers里,我们用R2存原始Markdown文件,用KV存索引与元数据,形成“对象存储+键值缓存”的混合模式。

  • R2 Bucket:命名为blog-content,存放所有.md文件,路径为posts/{slug}.md。例如posts/hello-world.md。
  • KV Namespace:命名为blog-index,存储:
    • posts:list→ JSON数组,含所有文章slug、title、date;
    • posts:{slug}:meta→ 文章元数据(作者、标签、摘要);
    • posts:{slug}:html→ 渲染后的HTML(带TTL 1小时,避免重复渲染)。

这样设计的好处:

  • R2按对象收费,存储成本极低($0.015/GB/月);
  • KV读写快(<10ms),适合高频查询;
  • 更新文章时,只需R2.put()新文件 +KV.put()更新元数据,天然原子性(KV写入成功即视为发布完成)。

我写了一个sync-posts脚本,用GitHub Actions监听content/目录变更,自动同步到R2和KV。关键代码片段:

// sync.ts const r2 = env.BLOG_CONTENT; const kv = env.BLOG_INDEX; // 读取GitHub API获取最新文件列表 const files = await fetch(`https://api.github.com/repos/user/blog/contents/content`, { headers: { 'Authorization': `token ${GITHUB_TOKEN}` } }).then(r => r.json()); for (const file of files) { if (file.name.endsWith('.md')) { const content = await fetch(file.download_url).then(r => r.text()); // 写入R2 await r2.put(`posts/${file.name.replace('.md', '')}.md`, new Blob([content])); // 解析frontmatter,写入KV const meta = parseFrontmatter(content); await kv.put(`posts:${file.name.replace('.md', '')}:meta`, JSON.stringify(meta)); } }

实测对比:纯KV存全文(最大10MB)会导致KV写入变慢(>100ms),而R2上传1MB文件仅需200ms。分离存储是性能与成本的最优解。

4.2 渲染服务层:Workers + Markdown解析器

博客页面由Workers动态渲染。请求/posts/hello-world时,Workers执行以下步骤:

  1. 从KV读取posts:hello-world:html,若命中则直接返回;
  2. 若未命中,从R2读取posts/hello-world.md;
  3. 用marked库(浏览器兼容版)解析Markdown为HTML;
  4. 注入全局CSS、Header、Footer,生成完整HTML;
  5. 将HTML写入KV(TTL 3600s),并返回Response。

关键优化点:

  • marked库需用marked/marked.min.js(压缩版),大小<20KB;
  • HTML模板用模板字符串拼接,避免引入大型模板引擎;
  • KV写入异步进行(kv.put().catch(console.error)),不影响主响应流。
export default { async fetch(request, env) { const url = new URL(request.url); const slug = url.pathname.split('/')[2]; // 1. 尝试KV缓存 let html = await env.BLOG_INDEX.get(`posts:${slug}:html`); if (html) { return new Response(html, { headers: { 'Content-Type': 'text/html' } }); } // 2. 读取R2 const mdObj = await env.BLOG_CONTENT.get(`posts/${slug}.md`); if (!mdObj) return new Response('Not Found', { status: 404 }); const md = await mdObj.text(); // 3. 渲染 const htmlContent = marked.parse(md); html = ` <!DOCTYPE html> <html><head><title>${slug}</title></head> <body>${htmlContent}</body></html> `; // 4. 缓存(异步) env.BLOG_INDEX.put(`posts:${slug}:html`, html, { expirationTtl: 3600 }); return new Response(html, { headers: { 'Content-Type': 'text/html' } }); } };

4.3 评论系统:Durable Objects 实现无数据库留言

传统评论需MySQL存数据、Redis缓存、Cron清理垃圾。在Workers里,我用Durable Object实现:

  • 每个文章对应一个DO实例,ID为post-${slug};
  • DO的fetch()方法处理POST /comment请求,将留言存入state.storage;
  • GET /comments返回所有留言(JSON数组);
  • alarm()每24小时自动清理7天前的留言。

DO代码精简到50行:

export class CommentService { constructor(state, env) { this.state = state; } async fetch(request) { const url = new URL(request.url); if (request.method === 'POST' && url.pathname === '/comment') { const data = await request.json(); const comments = (await this.state.storage.get('comments')) || []; comments.push({ ...data, at: Date.now() }); await this.state.storage.put('comments', comments); return new Response('OK'); } if (request.method === 'GET' && url.pathname === '/comments') { const comments = await this.state.storage.get('comments') || []; return new Response(JSON.stringify(comments), { headers: { 'Content-Type': 'application/json' } }); } } async alarm() { const comments = await this.state.storage.get('comments') || []; const filtered = comments.filter(c => Date.now() - c.at < 7 * 24 * 60 * 60 * 1000); await this.state.storage.put('comments', filtered); } }

前端用fetch()直接调用https://blog.edge/posts/hello-world/comments,无需CORS配置(同域)。DO的强一致性保证了留言顺序,而alarm()自动维护数据新鲜度。

4.4 部署与CI/CD:Wrangler + GitHub Actions一体化

整个博客的部署流程全自动:

  1. 开发者推送Markdown到GitHubcontent/目录;
  2. GitHub Action触发sync-posts.ts,更新R2/KV;
  3. 同时触发wrangler pages deploy,将静态资源(CSS/JS)部署到Pages;
  4. Workers脚本通过wrangler deploy更新(wrangler.toml中配置workers_dev = true)。

wrangler.toml关键配置:

name = "blog-edge" main = "./src/index.ts" compatibility_date = "2023-08-01" # 绑定R2 [[r2_buckets]] binding = "BLOG_CONTENT" bucket_name = "blog-content" # 绑定KV [[kv_namespaces]] binding = "BLOG_INDEX" id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # 绑定Durable Object [[durable_objects.bindings]] name = "COMMENT_SERVICE" class_name = "CommentService" script_name = "blog-edge"

避坑经验:

  • Wrangler CLI必须用v3.x(v2已废弃),且wrangler.toml格式与v2不兼容;
  • R2 bucket创建后,需在Dashboard中手动设置“Public Read”权限,否则Workers无法读取;
  • Durable Object的script_name必须与当前Workers名称一致,否则stub无法解析。

这套架构上线后,博客首页TTFB稳定在30ms内(全球边缘节点),日均处理2万请求,月成本约$0.8(R2存储+KV读写+Workers执行),远低于一台DigitalOcean Droplet($5/月)。它不是“玩具项目”,而是经过生产验证的边缘原生范式。

5. 何时该用“cloudflare-os”,何时该回归传统服务器?

“cloudflare-os”很酷,但绝不万能。我参与过12个客户项目,其中7个最终选择Workers,5个退回传统服务器。关键决策点不在技术炫酷度,而在业务逻辑与基础设施的耦合深度。下面用一张决策矩阵帮你快速判断。

5.1 适合Workers(“cloudflare-os”)的五大典型场景

场景核心特征Workers优势实例
边缘API网关请求量大、逻辑简单、需低延迟全球边缘执行,无网络跳转,冷启动<5ms认证代理、GraphQL聚合、A/B测试分流
静态内容增强原为纯静态站点,需动态能力Pages + Workers无缝集成,无需改架构博客评论、表单提交、个性化Header
无状态微服务业务逻辑独立、无共享状态、高并发自动扩缩容,按请求计费,运维零成本图片压缩、PDF生成、Markdown渲染
事件驱动工作流由外部事件触发(Webhook、Cron、MQ)与Cloudflare生态深度集成,事件源丰富GitHub Sync、定时报告、IoT数据清洗
前端基础设施前端团队主导,后端资源有限前端工程师可全栈掌控,无需后端介入环境变量注入、Feature Flag、灰度发布

我帮一家电商公司做的价格监控服务,就是典型边缘API网关:每天从50个竞品网站抓取价格,通过Workers聚合后,提供GET /price/{sku}接口。整个服务QPS峰值2000,TTFB<40ms,月成本$1.2。若用EC2部署,需至少2台t3.medium($15/月)+ CloudWatch告警 + Auto Scaling配置,运维复杂度指数级上升。

5.2 必须回归传统服务器的四大红区

红区根本原因替代方案风险提示
需要TCP/UDP长连接Workers无Socket APINode.js/Go服务部署在VPS或K8sWebSocket聊天室、游戏服务器、MQTT Broker
依赖特定系统库Workers无fs/child_process/osDocker容器化部署FFmpeg视频转码、LibreOffice文档转换、Python科学计算
强事务一致性要求KV/R2无ACID事务,DO跨实例无分布式事务PostgreSQL集群 + 应用层事务管理银行转账、库存扣减、订单支付
定制化网络协议栈Workers仅支持HTTP/HTTPS自建eBPF程序或DPDK应用CDN边缘缓存策略、DDoS防护、QUIC协议实验

曾有个客户坚持用Workers做在线协作文档(类似Notion),要求实时同步、冲突解决、历史版本。我们尝试用DO存文档状态,但发现:

  • 多用户同时编辑时,DO的单实例锁导致高延迟;
  • 历史版本存储在KV中,查询效率低下;
  • 无法实现端到端加密(因Workers无法访问私钥文件)。
    最终方案是:Workers只做身份认证和路由,核心协作引擎用WebAssembly编译的Yjs库在浏览器端运行,后端用Node.js+PostgreSQL处理持久化。Workers在这里退回到“边缘门面”的角色,恰到好处。

5.3 混合架构:让“cloudflare-os”与传统服务器各司其职

最务实的方案,往往是混合架构。我目前维护的三个生产系统,全部采用“Workers前置 + 服务器后置”模式:

  • 流量入口:Workers处理SSL终止、WAF规则、Geo路由、Bot管理;
  • 业务逻辑:简单逻辑(鉴权、日志、缓存)在Workers,复杂逻辑(AI推理、大数据分析)调用后端API;
  • 状态存储:KV/R2存边缘缓存,PostgreSQL/Redis存核心数据;
  • 运维监控:Workers日志导出到Datadog,后端指标由Prometheus采集。

例如一个AI客服系统:

  • 用户消息经Workers过滤(去重、敏感词检测、会话ID注入);
  • 转发到后端FastAPI服务,调用LLM模型;
  • 响应返回Workers,添加Cache-Control: public, max-age=300;
  • 同时将对话摘要写入R2,供运营看板查询。

这样,Workers承担了它最擅长的“边缘智能”,而把重负载留给更灵活的服务器。“cloudflare-os”不是操作系统的替代品,而是操作系统在边缘网络的延伸——它拓展了你的架构半径,而非取代你的技术栈。

我在实际使用中发现,最高效的团队不是“全Workers”或“全服务器”,而是能清晰划分边界的团队:前端工程师负责Workers层,后端工程师负责服务器层,SRE负责两者间的API契约与SLA保障。当每个人都清楚自己的“操作系统”边界在哪里,协作反而更顺畅。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进