这篇文档介绍的是一个对于 Durable Objects 来说至关重要的功能:WebSocket 休眠 API (WebSocket Hibernation API)。这个 API 彻底改变了在 Durable Objects 中使用 WebSocket 的成本和效率。
我们先来理解它解决了什么根本问题,然后再看它是如何工作的。
1. 核心问题:昂贵的“清醒”状态
想象一下你之前的那个“专属管家”(Durable Object)。
-
传统 WebSocket 模式:如果一个客户通过 WebSocket(一条持续打开的电话线)连接到这个管家,那么只要这条电话线不断开,管家就必须一直“醒着”待命。他不能去休息(无法关闭实例)。
-
问题所在:Durable Object 的计费是按“活跃持续时间”来的。如果一个 WebSocket 连接保持 24 小时,哪怕一句话都不说,你的管家也得“清醒”24 小时,这会导致极高的持续时间费用 (Duration cost)。对于一个需要支持成百上千个并发连接的聊天室来说,这种成本是无法接受的。
WebSocket 休眠 API 的目标:打破“WebSocket 连接存在”与“Durable Object 必须活跃”之间的强制绑定,允许对象在 WebSocket 连接保持的情况下进入“休眠”状态,从而大幅节约成本。
2. 解决方案:将 WebSocket “托管”给网络
WebSocket 休眠 API 的工作原理,就像是管家把电话转接到了一个智能总机(Cloudflare 全球网络)上。
-
接受并“托管” (Accept & Hibernate):当一个客户端请求建立 WebSocket 连接时,管家(Durable Object)接起电话,但不是一直拿着它,而是告诉智能总机:“这个客户你帮我看着,有事再叫我。” 然后管家就可以回去睡觉了(实例被关闭,停止计费)。
-
按需唤醒 (Wake on Demand):
-
当客户说话时:如果客户通过 WebSocket 发送了一条消息,智能总机(网络)会立刻叫醒对应的管家,并把消息递给他处理。
-
当管家需要主动说话时:如果管家因为其他原因(比如另一个用户发了广播消息)需要给这个客户发送消息,他可以告诉总机:“帮我找到A客户的电话线,我要跟他说话。”
-
-
处理结束,继续休眠:管家处理完单个消息后,就可以再次回去睡觉,把连接继续交给总机看管。
这样一来,Durable Object 只在有实际消息需要处理的瞬间才被唤醒并计费,而不是在整个连接期间都保持活跃。
3. API 的具体用法和关键组件
根据您提供的文档,实现这一点的关键 API 如下:
acceptWebSocket() - 接受并标记连接
这是启动休眠的第一步。在你的 fetch 处理器中,当你接受 WebSocket 连接时,可以给它附加标签 (tags)。
// 在 fetch() 方法中
export default {
async fetch(request, env, ctx) {
// ...
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
// 关键步骤:接受连接,并附加标签
this.ctx.acceptWebSocket(server, ["chat", "room:general"]); // "server" 是 DO 这一侧的 socket
// 返回客户端的 socket,并立即结束 fetch 处理,允许对象休眠
return new Response(null, { status: 101, webSocket: client });
}
}
-
acceptWebSocket(socket, tags):这个方法告诉运行时:“请接管这个socket。如果它收到消息或关闭,请用下面定义的事件处理器来唤醒我。” -
tags: 标签是一个字符串数组,非常重要。它就像给这个连接打上了“属于 general 聊天室”的标签,方便以后按标签查找。
getWebSockets() - 获取休眠中的连接
这个方法允许你从“智能总机”那里把托管的 WebSocket 连接找回来,以便主动给它们发消息(例如广播)。
// 示例:广播消息给一个房间里的所有人
async broadcast(message) {
// 按标签查找所有属于 "room:general" 的休眠中的连接
const sockets = this.ctx.getWebSockets({ tag: "room:general" });
// 遍历并发送消息
for (const ws of sockets) {
ws.send(message);
}
}
getWebSockets({ tag: "..." }):可以按单个标签或多个标签来过滤,获取所有匹配的 WebSocket 实例。
新的事件处理器 - 处理唤醒事件
当一个被托管的 WebSocket 收到消息或关闭时,Durable Object 会被唤醒,并调用下面这些新的顶级事件处理器(这些方法要和 fetch 并列定义在类中)。
-
webSocketMessage(ws, message): 当休眠的ws收到message时被调用。async webSocketMessage(ws, message) { // 可以在这里处理收到的消息,例如存入数据库,或者广播给其他人 // ws.send("You said: " + message); // await this.broadcast(message); } -
webSocketClose(ws, code, reason, wasClean): 当休眠的ws连接关闭时被调用。你可以在这里进行清理工作,比如更新在线用户列表。 -
webSocketError(ws, error): 当休眠的ws发生错误时被调用。
总结与优势
| API / Handler | 用途 | 比喻 |
| :--- | :--- | :--- |
| acceptWebSocket(ws, tags) | 接受连接并将其“托管”给网络,同时打上标签。 | 管家把电话转接给总机,并告诉总机这是“聊天室”的电话。 |
| getWebSockets({tag: ...}) | 根据标签找回一个或多个被托管的连接。 | 管家让总机帮他找出所有“聊天室”的电话线,准备广播。 |
| webSocketMessage(ws, msg) | 当被托管的连接收到消息时,唤醒对象并执行此函数。 | 总机发现有客户说话,立刻叫醒管家,并把话传给他。 |
| webSocketClose() | 当连接关闭时,唤醒对象执行此函数。 | 总机发现客户挂了电话,通知管家一声。 |
核心优势:
-
极大降低成本:这是最主要的优势。费用从“按连接时长”变为“按实际消息处理时间”,成本可能降低几个数量级。
-
提升可扩展性:一个 Durable Object 实例可以轻松管理成千上万个并发 WebSocket 连接,因为它不需要在内存中维护所有连接,极大地减少了内存占用。
-
简化状态管理:开发者不再需要自己手动在
storage中存储和追踪所有 WebSocket 的状态,平台为你搞定了一切。