核心工具链WebSocket 与实时功能
WebSocket 与实时功能
用 Bun.serve 原生 WebSocket 实现连接管理、pub/sub、背压与保活
最后更新于
什么时候用
- 聊天、协作编辑、实时仪表盘、Agent 进度推送:Bun 原生 WebSocket 是一等选择。
- 需要 Socket.IO 的房间语义、自动重连和降级:
ws或 Socket.IO 包也能在 Bun 上跑,但要用客户端库自己实现广播,替代不了server.publish的原生 pub/sub。 - 目标是 Lambda、Cloudflare Workers 等平台:本页形态不适用,见 Serverless 与边缘。
最小回声服务
const server = Bun.serve({
port: 3000,
fetch(req, server) {
if (server.upgrade(req)) return; // 升级成功,不再返回 Response
return new Response('WebSocket endpoint', { status: 426 });
},
websocket: {
open(ws) {
ws.send('welcome');
},
message(ws, message) {
ws.send(message); // 回声
},
close(ws, code, reason) {
console.log('closed', code, reason);
},
},
});鉴权与连接上下文
升级前是普通 HTTP 请求,先鉴权再升级;上下文通过 data 注入,之后每个处理器都能读:
// 项目自己的鉴权:校验 Cookie/Token,返回用户 ID,失败返回 null
function authenticate(req: Request): string | null {
return req.headers.get('x-demo-user'); // 演示用;生产换成真实会话校验
}
Bun.serve<{ userId: string }>({
fetch(req, server) {
const userId = authenticate(req);
if (!userId) {
return new Response('unauthorized', { status: 401 });
}
if (server.upgrade(req, { data: { userId } })) return;
return new Response('WebSocket endpoint', { status: 426 });
},
websocket: {
// 用 data 属性为 ws.data 提供类型
data: {} as { userId: string },
open(ws) {
ws.subscribe(`user:${ws.data.userId}`);
},
message(ws, message) { /* ... */ },
},
});Pub/Sub
| API | 语义 |
|---|---|
ws.subscribe(topic) / ws.unsubscribe(topic) | 管理单个连接的主题订阅 |
ws.publish(topic, data) | 发给该主题所有订阅者,不含自己 |
server.publish(topic, data) | 发给该主题所有订阅者,包含自己 |
ws.send(data) | 只发给当前连接;返回发送字节数 |
ws.send 的返回值要检查:0 表示连接异常被丢弃,-1 表示已入队但触发背压。高频推送时用 backpressureLimit 与 closeOnBackpressureLimit 决定是缓冲还是断开慢消费者,不要用无限内存为慢客户端兜底。
单进程内存广播
server.publish 的 pub/sub 存在当前进程内存里。多副本部署时,跨副本消息需要 Redis Pub/Sub 等外部总线转发,见 数据库与存储 的 Redis 边界。
空闲超时与保活
Bun 默认关闭 120 秒无活动的 WebSocket 连接(idleTimeout 可配置),但"无活动"包括协议层 ping:Bun 默认 sendPings: true,会自动发 ping,标准客户端自动回 pong,所以健康的纯订阅连接不会因静默被断开。真正需要处理的场景:
- 代理链路另有空闲超时:Nginx、Cloudflare、平台 LB 各自掐断空闲连接,应用层仍应按链路最短者设计 30–60 秒心跳与自动重连;
- 僵尸对端:TCP 半开连接下 ping 也可能无响应,
idleTimeout就是兜底;客户端必须实现重连并恢复订阅; - 关闭了
sendPings或客户端不标准时,才需要自己周期性ws.send保活。
生产边界
- 用
maxPayloadLength限制单条消息大小;消息体按不可信输入校验。 - 升级请求记录鉴权结果,不在日志里写 token。
- 优雅退出时主动
ws.close()并停止接受新升级,配合 容器与 Kubernetes 的 SIGTERM 流程。 - 压测目标:连接数、每秒消息数、广播扇出和内存,而不是单连接回声延迟。
验收
- 未认证请求拿到 401,不出现升级后的匿名连接;
- 慢消费者触发背压策略,内存不随连接数失控;
- 空闲 120 秒边界下客户端能自动重连恢复订阅;
- 多副本场景经外部总线收到跨副本消息。
官方参考:Bun WebSocket、Bun.serve。