Cloudflare 发布 K2:无服务器事件流
K2 把生产者与消费者在扩展和时间上解耦:边缘持久缓冲、多消费者各自独立读取、按 key 保序,且兼容 Kafka 客户端。文章讲它跟 Queues、Pipelines 的分工与适用边界。
中文
复制

在传统的远程过程调用(RPC)架构中,有一个核心难题:生产者和消费者必须在规模和节奏上保持一致。如果生产者发送的数据超出消费者的处理能力,或者消费者及下游服务不可用,事件就会丢失。当多个消费者需要各自独立处理数据时,这个问题会更加严重。例如,一个电商后端会在交易完成时发出事件,这些事件需要被分析系统和欺诈检测服务读取。解决办法是解耦生产者和消费者——在中间插入一个服务,由它承接写入,同时让各个读取方按自己的节奏独立消费。今天我们推出 Cloudflare K2 公测版,正是为了解决这个问题。K2 是 Developer Platform 上的持久化事件流原语。你把事件发送到 K2 流,它会将这些事件存储为有序日志。消费者可以用多种方式读取,例如把读取任务拆分到一组消费者上,或者把全部消息投递给所有消费者。它完全无服务器,可扩展到海量数据,并支持长期保留,因此即使消费者长时间停机也不会丢失数据。在底层,K2 在 R2 对象存储之上实现了分区、持久化的日志,从而能够扩展到巨大的存储规模。如果你准备开始使用,可以按照这里的指南在几秒内创建你的第一个流。
边缘上的流
我们最初做 K2,是因为我们需要一个边缘上的持久缓冲,一开始是为了给 Basin Pipelines 做摄取层。Pipelines 由一个流处理引擎驱动,采用拉取模型,也就是说必须有另一个系统先把事件存起来,之后才能读取、转换并写入 R2。而一旦事件被 Pipelines Stream 接收,我们就承诺绝不丢弃,所以这个存储必须持久——也就是不能丢数据——而且要能撑过可能很长的时间。大多数公司到这一步会部署 Apache Kafka。但 Pipelines 跑在 Cloudflare 边缘上,横跨 335 多个城市的大量服务器。我们这套独特的架构意味着往往跑不了 Kafka 这类传统的分布式系统软件,必须重新思考这类系统该怎么构建和运维。对有状态服务来说尤其如此,Cloudflare 的全球基础设施带来了一些挑战:我们拿到的机器切片相对较小,这些机器相对短命,网络还常常走公网。但我们的基础设施也有几项超能力:无论用户身在何处都离他们很近,并且有惊人的横向扩展能力。在设计后来成为 K2 的这套持久缓冲系统时,我们决定依赖已有的强大状态原语:R2。像 R2 这样的对象存储系统,把极高持久性的存储(11 个 9!)和强一致的 API 结合在一起。把复制和共识下推给存储层,能让应用层(这里是 K2)变得简单得多、便宜得多、性能也高得多。附带的好处是计算与存储分离,两者可以各自独立扩展。这让我们能以低成本存下海量历史数据。怎么在对象存储之上构建日志?一个直接的问题是,R2 和其他对象存储一样不支持 append,而 append 是日志的标准操作。我们只能写完整的文件,也就是 segment,而且要足够大,才能摊薄每个文件读写带来的开销。做法是先在边缘服务的内存里累积写入。等一小段时间让数据到齐,再把所有事件写成一个 segment 文件。我们借助 R2 的原子操作实现有序和严格递增的 offset,不需要额外的协调服务。基于 R2 构建虽然好处很多,但有一个缺点:写入延迟更高。写对象存储比写本地磁盘慢,而且必须先等本地批次攒够才能开始写。在 K2 的首个版本里,这加起来大约是 99 分位响应时间下 1 秒的写入延迟。关于 K2 的设计细节,我们会在后续的技术深潜文章里分享更多。
Streams、Queues 还是 Pipelines?
Cloudflare 已有若干异步投递原语,包括 Queues 和 Basin Pipelines。什么时候该用 K2,而不是这些现成的产品?Queues 和 K2 Streams 表面上有些相似:都接收事件、持久化存储,再投递给消费者。Queues 的设计围绕追踪单个任务项——那些昂贵或耗时、需要异步完成的工作。比如图像处理应用可以把用户请求入队,交给真正的图像处理服务去处理。它支持针对单个工作项的复杂逻辑,比如重试、延迟,以及失败后的死信队列。K2 则不同,它面向的是大规模数据搬运、长期保留和扇出消费。消息按批次生产和消费——换来高效处理,代价是没法做消息级重试。这种批处理也让生产者的延迟高于 Queues。Basin Pipelines 是无服务器摄取服务。你把 Pipeline 的 JSON 事件发过去,经过转换后写入 R2 或 Basin Catalog。如果最终目的是把事件写进对象存储或 Iceberg 表,我们推荐 Pipelines;如果要做自定义处理,或写到其他目的地,就用 K2。
上手
用 K2 得先创建 stream。一个账号下可以有多个 stream,对应不同的用例或事件类型。创建方式有 cf、Wrangler、控制台或 API。以收集和处理产品分析数据为例。先用 cf 创建一个 stream:
$ cf k2 streams create --name app_events --http-enabled
{
"id": "d78b09ee1f50430e9ec92a8af92b0231",
"name": "app_events",
"retention_seconds": 604800,
"endpoint": "https://d78b09ee1f50430e9ec92a8af92b0231.k2.cloudflarestorage.com",
"http": {
"enabled": true,
"authentication": false
},
"worker_binding": {
"enabled": true
},
"created_at": "2026-09-28T15:14:39.053Z",
"modified_at": "2026-09-28T15:14:39.053Z"
}
有了 stream,就可以往里生产数据了,通过 HTTP API 或 Worker binding 都行。比如在 Worker 里:
const result = await env.EVENTS.send([
{
content: new TextEncoder().encode(
JSON.stringify({
event: "page_view",
path: new URL(request.url).pathname,
timestamp: Date.now(),
}),
),
headers: { "content-type": "application/json" },
},
]);
if (!result.success) {
console.error(`Produce failed: ${result.error.message}`);
return new Response("Failed to record event", {
status: result.error.retryable ? 503 : 500,
});
}
K2 把数据表示为字节,所以格式和编码随你的应用需要来定。
现在 stream 里有了事件,就可以创建 subscription 了。Subscription 把工作分配给多个消费者,实现 读并行——横向扩展到多个读取者,承受单台服务器扛不住的负载。
我们可以通过 HTTP API 创建订阅。
$ curl -X POST "https://d78b09ee1f50430e9ec92a8af92b0231.k2.cloudflarestorage.com/subscriptions" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{
"name": "analytics_processor",
"start_at": { "type": "earliest" }
}'
{
"result": { "id": "ee13f761783d3823a447a47b572ebf76" },
"success": true,
"errors": [],
"messages": []
}
订阅创建好之后,就可以在每个消费者里轮询它:
$ curl -X POST \"https://4d8f5394e3e733debdeca9c65c5b7439.k2.cloudflarestorage.com/subscriptions/ee13f761783d3823a447a47b572ebf76/consume" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{
"worker_id": "analytics-1",
"max_records": 100
}'
{
"result": {
"batch_id": "b7e4c9210a3f468d95c2e1068fdb734a",
"leased_until_ms": 1790633929437,
"records": [
{
"timestamp_ms": 1790633629168,
"content": "eyJldmVudCI6InBhZ2VfdmlldyJ9",
"headers": {
"content-type": "application/json"
}
},
...
]
},
"success": true,
"errors": [],
"messages": []
}
当客户端调用 consume 时,会拿到这批事件的一份 lease,有效期 5 分钟。客户端可以做三件事之一:
- ack,把这批事件标记为已处理,确保不会被重新投递
- nack(negative ack),表示处理失败,希望重新投递
- extend,延长 lease,用于处理时间不够的情况
$ curl -X POST "https://d78b09ee1f50430e9ec92a8af92b0231.k2.cloudflarestorage.com/subscriptions/ee13f761783d3823a447a47b572ebf76/batches/b7e4c9210a3f468d95c2e1068fdb734a/ack" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{ "worker_id": "analytics-1" }'
这是从 K2 消费的一种方式:把工作拆分给多个消费者,每个消费者只拿到一部分数据。另一种方式是每个消费者各用一个订阅——也就是 pub/sub 模式——这样每个消费者都能看到全部消息。两种方式也可以混用,维护多个相互独立的消费者池。
API 的完整细节见 K2 文档。
定价与可用性
K2 今天起进入公开测试,拥有 Workers Paid 订阅的账户即可使用,限制如下:
- 存储用量上限 10GB
- 每个 stream 写入 30 MB/s
如果需要更高的限额,请在 Discord 上联系团队,或填写限额提升表单。
测试期间 K2 的使用不收费。开始计费后,我们预计的定价如下:
| 定价 | |
|---|---|
| 写入数据 | $0.04 / GB |
| 读取数据 | $0.04 / GB |
| 数据保留 | $0.02 / GB / 月 |
下一步
未来几个月 K2 有一份令人期待的路线图,包括:
- 更高的写入并行度,stream 可达多 GB/s
- 消息 key 以及基于 key 的顺序保证
- 基于 push 的 worker 消费者
- Express 层,更低的写入延迟和端到端延迟
- 直接兼容 Apache Kafka 客户端
期待看到你在 K2 上做出什么!欢迎在 Cloudflare Discord 上分享反馈。