⚡ 实时通信

WebSocket / SSE / CRDT / 实时协作 — 让数据流动起来
前端层 实时 WebSocket SSE CRDT 协作 ← 多 Agent 协作
← SaaS 全栈知识库
📑 目录

实时通信是什么?为什么 SaaS 离不开它

实时通信是指客户端与服务器(或客户端之间)在数据产生后立即传递的能力,而非依赖客户端定时轮询(Polling)。在现代 SaaS 产品中,实时性已经从"锦上添花"变成了"基础能力"。

🎯 哪些场景需要实时通信?

如果不使用实时通信,替代方案是轮询(Polling):客户端每隔 N 秒请求一次。问题显而易见:

指标轮询(5s 间隔)WebSocketSSE
延迟平均 2.5s,最大 5s<100ms<100ms
请求数/用户/分钟121(长连接)1(长连接)
服务器负载高(大量空响应)低(仅推送变更)低(仅推送变更)
带宽浪费高(HTTP 开销重复)
电池消耗(移动端)严重

传输层:WebSocket vs SSE vs Long Polling

三种主要的实时通信传输方式,各有适用场景:

WebSocket — 全双工持久连接

协议原理

WebSocket 通过 HTTP Upgrade 握手升级为持久的全双工 TCP 连接。握手完成后,客户端和服务器可以在同一条连接上双向自由发送消息,无需 HTTP 请求-响应模式。

// 握手过程
客户端 → 服务器: HTTP GET /ws
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

服务器 → 客户端: HTTP 101 Switching Protocols
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

// 握手后:全双工通信
客户端 ←→ 服务器: 二进制帧或文本帧

✅ 优势

❌ 劣势

Server-Sent Events (SSE) — 服务器单向推送

协议原理

SSE 基于 HTTP 长连接,服务器通过 text/event-stream Content-Type 持续推送事件给客户端。本质是一个永远不会结束的 HTTP 响应

// 服务端 (Node.js)
res.writeHead(200, {
  'Content-Type': 'text/event-stream',
  'Cache-Control': 'no-cache',
  'Connection': 'keep-alive'
});

// 发送事件
res.write('event: message\n');
res.write('data: {"text":"hello"}\n');
res.write('id: 123\n\n');  // ID 用于自动重连续传

// 客户端 (浏览器)
const es = new EventSource('/sse');
es.addEventListener('message', (e) => {
  console.log(JSON.parse(e.data));
});
// 断线自动重连!浏览器原生支持

✅ 优势

❌ 劣势

Long Polling — 兼容性最后的兜底

客户端发起 HTTP 请求,服务器不立即返回,而是挂起直到有数据或超时。返回后客户端立即发起新请求。

传输层对比总览

维度WebSocketSSELong Polling
方向全双工 ↔服务器→客户端 ↓服务器→客户端 ↓(模拟)
延迟极低
数据格式文本 + 二进制仅文本任意 HTTP
自动重连需手动实现浏览器原生需手动实现
HTTP/2独立连接可复用可复用
代理/CDN可能有问题友好友好
连接开销1 TCP 连接1 HTTP 连接N 次 HTTP 请求
最佳场景聊天、游戏、协作推送、流式 AI兜底兼容

WebSocket 深度实战

服务端:Node.js + ws

import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

// 连接管理
const clients = new Map(); // ws → { userId, rooms: Set }

wss.on('connection', (ws, req) => {
  // 1. 认证:从 query 或 header 提取 token
  const token = new URL(req.url, 'http://localhost').searchParams.get('token');
  const user = verifyToken(token);
  if (!user) { ws.close(4001, 'Unauthorized'); return; }

  // 2. 注册
  clients.set(ws, { userId: user.id, rooms: new Set() });

  // 3. 心跳
  ws.isAlive = true;
  ws.on('pong', () => { ws.isAlive = true; });

  // 4. 消息处理
  ws.on('message', (raw) => {
    const msg = JSON.parse(raw);
    handleMessage(ws, msg);
  });

  ws.on('close', () => {
    clients.delete(ws);
    broadcastPresence(user.id, 'offline');
  });
});

// 心跳检测(30s 间隔)
const heartbeat = setInterval(() => {
  wss.clients.forEach((ws) => {
    if (!ws.isAlive) return ws.terminate();
    ws.isAlive = false;
    ws.ping();
  });
}, 30000);

wss.on('close', () => clearInterval(heartbeat));

// 消息路由
function handleMessage(ws, msg) {
  const client = clients.get(ws);
  switch (msg.type) {
    case 'join_room':
      client.rooms.add(msg.roomId);
      broadcastToRoom(msg.roomId, {
        type: 'user_joined',
        userId: client.userId
      });
      break;
    case 'chat':
      // 持久化 + 广播
      saveMessage(msg.roomId, client.userId, msg.text);
      broadcastToRoom(msg.roomId, {
        type: 'chat',
        userId: client.userId,
        text: msg.text,
        timestamp: Date.now()
      });
      break;
  }
}

// 房间广播
function broadcastToRoom(roomId, data) {
  const payload = JSON.stringify(data);
  clients.forEach((client, ws) => {
    if (client.rooms.has(roomId) && ws.readyState === 1) {
      ws.send(payload);
    }
  });
}

服务端:Python + FastAPI

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from typing import Dict, Set

app = FastAPI()

class ConnectionManager:
    def __init__(self):
        self.active: Dict[str, Set[WebSocket]] = {}  # room → connections

    async def connect(self, ws: WebSocket, room: str):
        await ws.accept()
        if room not in self.active:
            self.active[room] = set()
        self.active[room].add(ws)

    def disconnect(self, ws: WebSocket, room: str):
        self.active[room].discard(ws)

    async def broadcast(self, room: str, message: dict):
        for ws in self.active.get(room, set()):
            await ws.send_json(message)

manager = ConnectionManager()

@app.websocket("/ws/{room}/{user_id}")
async def ws_endpoint(ws: WebSocket, room: str, user_id: str):
    await manager.connect(ws, room)
    await manager.broadcast(room, {"type": "join", "user": user_id})
    try:
        while True:
            data = await ws.receive_json()
            await manager.broadcast(room, {
                "type": "message",
                "user": user_id,
                "text": data["text"],
            })
    except WebSocketDisconnect:
        manager.disconnect(ws, room)
        await manager.broadcast(room, {"type": "leave", "user": user_id})

客户端:原生 + 重连逻辑

class RobustWebSocket {
  constructor(url, options = {}) {
    this.url = url;
    this.reconnectInterval = options.reconnectInterval || 1000;
    this.maxReconnectInterval = options.maxReconnectInterval || 30000;
    this.heartbeatInterval = options.heartbeatInterval || 30000;
    this.handlers = {};
    this.reconnectAttempts = 0;
    this.connect();
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      this.reconnectAttempts = 0;
      this.reconnectInterval = 1000;
      this.startHeartbeat();
      this.emit('open');
    };

    this.ws.onmessage = (e) => {
      const data = JSON.parse(e.data);
      this.emit('message', data);
      this.emit(data.type, data);  // 按类型分发
    };

    this.ws.onclose = (e) => {
      this.stopHeartbeat();
      if (e.code !== 1000) {  // 非正常关闭 → 重连
        this.scheduleReconnect();
      }
      this.emit('close', e);
    };

    this.ws.onerror = (e) => {
      this.emit('error', e);
    };
  }

  scheduleReconnect() {
    const delay = Math.min(
      this.reconnectInterval * Math.pow(1.5, this.reconnectAttempts),
      this.maxReconnectInterval
    );
    this.reconnectAttempts++;
    setTimeout(() => this.connect(), delay);
  }

  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.ws.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify({ type: 'ping' }));
      }
    }, this.heartbeatInterval);
  }

  stopHeartbeat() {
    clearInterval(this.heartbeatTimer);
  }

  send(data) {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
    }
  }

  on(event, handler) {
    if (!this.handlers[event]) this.handlers[event] = [];
    this.handlers[event].push(handler);
  }

  emit(event, ...args) {
    (this.handlers[event] || []).forEach(h => h(...args));
  }
}

// 使用
const ws = new RobustWebSocket('ws://localhost:8080?token=xxx');
ws.on('chat', (data) => {
  appendMessage(data.userId, data.text);
});
ws.on('open', () => {
  ws.send({ type: 'join_room', roomId: 'general' });
});

Socket.IO — WebSocket 的生产级封装

Socket.IO 是 WebSocket 的超集,提供:

⚠️ 注意:Socket.IO 不是 WebSocket。它有自己的协议,浏览器原生 WebSocket 客户端无法连接 Socket.IO 服务器。如果需要标准 WebSocket 互操作,不要用 Socket.IO。

// 服务端
import { Server } from 'socket.io';

const io = new Server(3000, {
  cors: { origin: '*' }
});

io.on('connection', (socket) => {
  console.log(`User connected: ${socket.id}`);

  // 加入房间
  socket.on('join', (roomId) => {
    socket.join(roomId);
    socket.to(roomId).emit('user_joined', { id: socket.id });
  });

  // 聊天消息
  socket.on('chat', (data) => {
    io.to(data.roomId).emit('chat', {
      userId: socket.id,
      text: data.text,
      timestamp: Date.now()
    });
  });

  // 断线
  socket.on('disconnect', (reason) => {
    console.log(`Disconnected: ${socket.id} (${reason})`);
  });
});

// 向特定房间广播
io.to('room-123').emit('notification', { text: 'Hello room!' });

Server-Sent Events 实战

SSE 最适合的场景是服务器单向推送——通知、流式 AI、实时指标。

SSE + LLM 流式输出

// Next.js API Route: 流式返回 LLM 生成内容
export async function POST(req: Request) {
  const { prompt } = await req.json();

  const stream = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: [{ role: 'user', content: prompt }],
    stream: true,
  });

  const encoder = new TextEncoder();
  const readable = new ReadableStream({
    async start(controller) {
      for await (const chunk of stream) {
        const content = chunk.choices[0]?.delta?.content || '';
        if (content) {
          // SSE 格式
          controller.enqueue(
            encoder.encode(`data: ${JSON.stringify({ content })}\n\n`)
          );
        }
      }
      controller.enqueue(encoder.encode('data: [DONE]\n\n'));
      controller.close();
    },
  });

  return new Response(readable, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    },
  });
}

// 客户端消费
const response = await fetch('/api/chat', {
  method: 'POST',
  body: JSON.stringify({ prompt }),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const text = decoder.decode(value);
  // 解析 SSE data: 行
  const lines = text.split('\n').filter(l => l.startsWith('data: '));
  for (const line of lines) {
    const data = line.slice(6);
    if (data === '[DONE]') break;
    const { content } = JSON.parse(data);
    appendToUI(content);  // 逐字追加到 UI
  }
}

SSE + Vercel AI SDK

// app/api/chat/route.ts (Next.js App Router)
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({ model: openai('gpt-4o'), messages });
  return result.toDataStreamResponse();  // 自动 SSE 格式
}

// 客户端
'use client';
import { useChat } from 'ai/react';

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();
  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>{m.role}: {m.content}</div>
      ))}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
      </form>
    </div>
  );
}

SSE 用于实时通知推送

// 服务端:推送通知
export function createNotificationStream(userId: string) {
  const encoder = new TextEncoder();

  return new ReadableStream({
    start(controller) {
      // 监听 Redis Pub/Sub
      const sub = redis.subscribe(`notifications:${userId}`);
      sub.on('message', (channel, message) => {
        controller.enqueue(
          encoder.encode(`event: notification\ndata: ${message}\n\n`)
        );
      });

      // 心跳(防止连接超时)
      const heartbeat = setInterval(() => {
        controller.enqueue(encoder.encode(':heartbeat\n\n'));
      }, 15000);

      // 清理
      controller.enqueue = (...args) => {
        if (controller.desiredSize === null) {
          clearInterval(heartbeat);
          sub.unsubscribe();
          return;
        }
        return ReadableStream.prototype.enqueue.apply(controller, args);
      };
    }
  });
}
💡 SSE 心跳很重要:代理/CDN/负载均衡器通常 30-60s 无数据就关闭连接。每 15-30s 发送一个 SSE 注释行(: heartbeat\n\n)可以保持连接活跃。SSE 注释以 : 开头,客户端 EventSource 会自动忽略。

CRDT:实时协作的核心算法

CRDT(Conflict-free Replicated Data Types,无冲突复制数据类型)是实时协作的数学基础。它解决了核心问题:多人同时编辑同一份数据,如何保证最终一致且不丢失信息

为什么需要 CRDT?

传统方案:中央服务器仲裁 客户端A ──编辑──→ 服务器 ──推送──→ 客户端B 客户端B ──编辑──→ 服务器 ──推送──→ 客户端A ❌ 问题:如果 A 和 B 同时编辑,服务器需要决定以谁为准 → 锁/冲突/丢失 CRDT 方案:数学保证无冲突 客户端A ──编辑──→ 本地 CRDT ──同步──→ 客户端B 的 CRDT 客户端B ──编辑──→ 本地 CRDT ──同步──→ 客户端A 的 CRDT ✅ 数学保证:无论同步顺序如何,最终状态一致

CRDT 的核心原理

1. 交换律(Commutativity)

操作的顺序不影响最终结果:f(a, b) = f(b, a)

例:两个用户同时在不同位置插入文字,合并结果与处理顺序无关。

2. 结合律(Associativity)

操作分组不影响结果:f(f(a, b), c) = f(a, f(b, c))

3. 幂等性(Idempotency)

同一操作执行多次等于执行一次:f(a, a) = a

例:收到重复的网络包不会导致数据错误。

CRDT 数据类型

类型用途代表实现
G-Counter只增计数器(页面浏览、点赞)每个节点独立计数,合并时取 max
PN-Counter可增减计数器G-Counter + G-Counter(正负分离)
G-Set只增集合(标签、已读消息 ID)合并取并集
OR-Set可增删集合(协作者列表)每个元素带唯一 tag,删除需移除 tag
LWW-Register最后写入胜出的值每个值附时间戳,合并取最新
RGA有序列表(文本编辑)每个字符带唯一 ID 和逻辑时钟
Yjs YText/YMap/YArray丰富文本/结构化数据Yjs 自有编码,高效压缩

CRDT vs OT

维度CRDTOT(Operational Transform)
需要中央服务器
离线支持天然支持复杂
理论正确性数学证明需要正确实现 transform 函数
内存/带宽较高(元数据多)较低
实现复杂度用库就行自己写 transform 很难
代表产品Yjs、Automerge、FigmaGoogle Docs
推荐✅ 新项目首选仅在特殊场景
💡 为什么推荐 CRDT 而非 OT?OT 需要中央服务器排序操作,且 transform 函数的正确性极难保证(Google 花了多年才做对)。CRDT 的数学性质保证了正确性,加上 Yjs/Automerge 这样的成熟实现,开发成本远低于 OT。

Yjs — 最流行的 CRDT 实现

Yjs 是 JavaScript 生态最成熟的 CRDT 库,周下载量 90 万+。它提供与原生 JS 类型一致的 Shared Types,但自动同步、自动合并

核心概念

import * as Y from 'yjs';

// 1. 创建文档
const doc = new Y.Doc();

// 2. 使用 Shared Types(和普通 JS 类型一样操作)
const ytext = doc.getText('content');
const ymap = doc.getMap('metadata');
const yarray = doc.getArray('items');

// 3. 操作 — 和原生类型几乎一样
ytext.insert(0, 'Hello ');
ytext.insert(6, 'World');

ymap.set('title', 'My Doc');
ymap.set('version', 1);

yarray.push([{ name: 'Item 1' }]);
yarray.insert(0, [{ name: 'Item 0' }]);

// 4. 监听变化
ytext.observe(event => {
  console.log('Text changed:', ytext.toString());
  event.delta.forEach(change => {
    // { insert: string } | { delete: number } | { retain: number }
  });
});

多端同步

import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';

const doc = new Y.Doc();

// 连接到 WebSocket 服务器
const provider = new WebsocketProvider(
  'wss://your-server.com',
  'room-name',
  doc
);

// 同步状态
provider.on('status', event => {
  console.log(event.status); // 'connecting' | 'connected' | 'disconnected'
});

// 自动同步!编辑 ytext → 其他客户端自动收到
const ytext = doc.getText('content');
ytext.insert(0, 'Hello from client A!');

Yjs + React

import { useYjs } from 'react-yjs';
import * as Y from 'yjs';

function CollaborativeEditor({ roomId }) {
  const doc = useYjs(roomId);
  const ytext = doc.getText('content');

  return (
    <textarea
      value={ytext.toString()}
      onChange={(e) => {
        // 简化版:实际需要用 ytext.delete + ytext.insert 处理差异
        doc.transact(() => {
          ytext.delete(0, ytext.length);
          ytext.insert(0, e.target.value);
        });
      }}
    />
  );
}

Yjs 服务端

// 最简 Yjs WebSocket 服务器
import { Server } from 'ws';
import * as Y from 'yjs';
import { setupWSConnection } from 'y-websocket/bin/utils';

const wss = new Server({ port: 1234 });

const docs = new Map(); // room → Y.Doc

wss.on('connection', (ws, req) => {
  const room = new URL(req.url, 'http://localhost').searchParams.get('room');
  if (!docs.has(room)) docs.set(room, new Y.Doc());
  const doc = docs.get(room);

  setupWSConnection(ws, req, { doc });
  // y-websocket 自带:同步、感知、更新广播
});

Yjs 的设计亮点

Automerge — 数据版本控制

Automerge 是 CRDT 领域的学术派代表,由 Martin Kleppmann(Cambridge 教授,《Designing Data-Intensive Applications》作者)团队开发。核心理念:给你的数据做版本控制,像 Git 一样

import * as Automerge from '@automerge/automerge';

// 1. 创建文档
let doc = Automerge.init();
doc = Automerge.change(doc, d => {
  d.title = 'Meeting Notes';
  d.items = [];
  d.items.push({ text: 'Discuss roadmap', done: false });
});

// 2. 修改
doc = Automerge.change(doc, d => {
  d.items[0].done = true;
  d.items.push({ text: 'Review PRs', done: false });
});

// 3. 获取变更(类似 git diff)
const changes = Automerge.getChanges(prevDoc, doc);

// 4. 应用远端变更(类似 git merge)
doc = Automerge.applyChanges(doc, remoteChanges);

// 5. 查看历史
const history = Automerge.getHistory(doc);
// [{ change: { ... }, snapshot: { title: 'Meeting Notes', items: [...] } }, ...]

// 6. Fork + Merge(分支开发)
let fork = Automerge.fork(doc);
fork = Automerge.change(fork, d => { d.title = 'Updated Title'; });
doc = Automerge.merge(doc, fork);  // 合并回来

Yjs vs Automerge

维度YjsAutomerge
定位实时协作引擎数据版本控制
文本编辑优秀(YText + 编辑器集成)基础
结构化数据YMap/YArray任意 JSON
性能极致优化Rust 核心,够用
历史/时间旅行快照完整变更历史
学术验证经验证Isabelle 定理证明
生态丰富(编辑器、Provider)相对小
最佳场景协作文档编辑器离线优先的结构化数据同步

Liveblocks — 托管式实时协作

Liveblocks 是实时协作的托管服务平台——把 Yjs + Presence + 通知 + 持久化全部做成 API,你不需要自己搭 WebSocket 服务器。

// Next.js + Liveblocks
import { createClient } from '@liveblocks/client';
import { LiveblocksProvider, RoomProvider } from '@liveblocks/react';

const client = createClient({
  publicApiKey: 'pk_live_xxx',
});

function App() {
  return (
    <LiveblocksProvider client={client}>
      <RoomProvider id="my-room">
        <CollaborativeEditor />
      </RoomProvider>
    </LiveblocksProvider>
  );
}

// 使用 Storage(CRDT 数据)
import { useStorage, useSelf, useOthers } from '@liveblocks/react';

function CollaborativeEditor() {
  // CRDT 数据
  const text = useStorage(root => root.document.textContent);

  // 自己的信息
  const me = useSelf();
  // 其他人的信息
  const others = useOthers();

  return (
    <div>
      <div className="presence-bar">
        {others.map(user => (
          <Avatar key={user.id} color={user.info.color} />
        ))}
      </div>
      {/* 编辑器内容 */}
    </div>
  );
}

Liveblocks vs 自建 Yjs 服务

维度Liveblocks自建 Yjs
上手速度分钟级天级
成本按 MAU 计费(免费 250 MAU)服务器成本
控制力(SaaS)完全控制
功能协作+评论+通知一体仅同步,其他自建
数据位置Liveblocks 服务器你的服务器
离线支持有限完整(IndexedDB 等)

Presence(在线状态)系统

在线状态是实时应用中"看得见对方"的关键——光标位置、输入状态、头像颜色。它和 CRDT 数据不同:状态是短暂的、不需要持久化、最新值覆盖旧值

// Yjs Awareness 协议(y-protocols 自带)
import { awarenessProtocol } from 'y-protocols/awareness';

const awareness = new awarenessProtocol.Awareness(doc);

// 设置自己的状态
awareness.setLocalState({
  name: 'Alice',
  color: '#ff6b6b',
  cursor: { line: 10, col: 5 },
  selection: { from: 45, to: 52 },
  isTyping: true,
});

// 监听他人的状态
awareness.on('change', ({ added, updated, removed }) => {
  const states = awareness.getStates();
  // Map(clientId → { name, color, cursor, ... })
});

// 超时自动移除(默认 30s 无心跳)

Presence 设计要点

扩展性:从 100 到 100 万连接

阶段 1:单服务器(0 → 5,000 连接)

// 单进程 Node.js + ws/Socket.IO
// 单机约 5K-10K 连接(取决于消息频率)
const wss = new WebSocketServer({ port: 8080 });
// 直接进程内 Map 管理房间

阶段 2:多进程(5K → 50,000 连接)

// 使用 Redis Pub/Sub 做进程间通信
import { createClient } from 'redis';

const sub = createClient();
const pub = createClient();
await sub.connect();
await pub.connect();

// 订阅房间消息
sub.subscribe('room:123', (message) => {
  // 广播给本进程的连接
  broadcastToLocal('room:123', JSON.parse(message));
});

// 发送消息到房间
function publishToRoom(roomId, data) {
  pub.publish(`room:${roomId}`, JSON.stringify(data));
}

// 配合 pm2 cluster 或 Node.js cluster

阶段 3:多服务器(50K → 500,000+ 连接)

架构:Load Balancer → N × WebSocket Server → Redis Pub/Sub → DB Client ──ws──→ Nginx/HAProxy │ ┌─────────┼─────────┐ ▼ ▼ ▼ WS-Srv1 WS-Srv2 WS-Srv3 │ │ │ └──── Redis Pub/Sub ────┘ │ PostgreSQL 关键:Sticky Session 或无状态设计 - Sticky: 同一用户始终路由到同一 WS 服务器 - 无状态: 通过 Redis Pub/Sub 广播给所有服务器
// Nginx Sticky Session 配置
upstream websocket {
  ip_hash;  // 同一 IP → 同一后端
  server ws1:8080;
  server ws2:8080;
  server ws3:8080;
}

server {
  location /ws {
    proxy_pass http://websocket;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400s;  // 24h 超时
  }
}

阶段 4:大规模(500K+ → 百万级)

大规模实时架构关键决策

规模连接数架构关键成本
MVP<5K单服务器1 台 VPS
成长5K-50K多进程 + Redis2-4 台 VPS + Redis
规模化50K-500K多服务器 + Redis Pub/SubK8s 集群 + Redis Cluster
超大规模500K+µWS + 分片 + 边缘专用基础设施

Agent 系统中的实时通信

从我们的 Agent 架构研究 中,实时通信在 Agent 系统中扮演关键角色:

🔗 与 Agent 研究的关联

Agent → 用户的实时流

// Agent 执行过程的实时推送
// 使用 SSE(单向够用,Agent → 用户)

// 服务端
export async function POST(req: Request) {
  const { task } = await req.json();

  return new Response(
    new ReadableStream({
      async start(controller) {
        const encoder = new TextEncoder();

        function send(event: string, data: any) {
          controller.enqueue(
            encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`)
          );
        }

        // 1. 开始思考
        send('thinking', { step: 'analyzing', message: 'Analyzing task...' });

        // 2. 工具调用
        send('tool_call', { tool: 'web_search', args: { query: '...' } });

        // 3. 工具结果
        send('tool_result', { tool: 'web_search', result: '...' });

        // 4. 需要审批
        send('approval_needed', {
          action: 'exec',
          command: 'rm -rf /tmp/old-data',
          risk: 'high'
        });

        // 5. 等待用户审批...
        // (审批通过另一个 HTTP 端点提交)

        // 6. 完成
        send('done', { result: 'Task completed successfully' });
        controller.close();
      }
    }),
    {
      headers: {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
      },
    }
  );
}

多 Agent 间的实时消息

// 使用 Redis Pub/Sub 做跨 Agent 通信
// 类似 AutoGen 的 Actor Model

class AgentBus {
  private redis: Redis;
  private agentId: string;

  async publish(targetAgent: string, message: AgentMessage) {
    await this.redis.publish(
      `agent:${targetAgent}`,
      JSON.stringify({ from: this.agentId, ...message })
    );
  }

  async subscribe(handler: (msg: AgentMessage) => void) {
    const sub = this.redis.duplicate();
    await sub.subscribe(`agent:${this.agentId}`, (raw) => {
      handler(JSON.parse(raw));
    });
  }

  // 广播给所有 Agent
  async broadcast(message: AgentMessage) {
    await this.redis.publish('agent:broadcast', JSON.stringify(message));
  }
}

选型决策树

你需要实时通信? │ ├─ 只需要服务器→客户端推送? │ ├─ 是 → SSE │ │ ├─ 只是 LLM 流式输出?→ SSE + Vercel AI SDK │ │ ├─ 只是通知推送?→ SSE + Redis Pub/Sub │ │ └─ 需要高频率二进制数据?→ WebSocket │ │ │ └─ 否 → WebSocket │ ├─ 不想管连接/重连?→ Socket.IO │ └─ 想要轻量/标准?→ ws + 自己写重连 │ ├─ 需要多人协作编辑? │ ├─ 文本编辑器?→ Yjs + 编辑器绑定 │ ├─ 结构化数据(JSON)?→ Automerge 或 Yjs YMap │ ├─ 不想搭服务器?→ Liveblocks │ └─ 离线优先?→ Yjs + IndexedDB Provider │ └─ 需要在线状态(光标/头像)? ├─ 已用 Yjs?→ y-protocols Awareness ├─ 已用 Socket.IO?→ 内置 Presence └─ 通用方案?→ 自定义 WebSocket 状态广播

🎯 快速推荐

你的场景推荐方案
LLM 聊天界面SSE + Vercel AI SDK
即时通讯/聊天室WebSocket (ws 或 Socket.IO)
协作文档编辑Yjs + y-websocket + 编辑器集成
协作白板/设计工具Yjs + Liveblocks
实时仪表盘SSE + 定时推送
Agent 状态推送SSE(单向够了)
多人游戏WebSocket + 游戏同步协议
离线优先应用Automerge 或 Yjs + 本地存储

替代方案与补充工具

🔌 Socket.IO
WebSocket 超集,自动降级+重连+房间。适合不想处理底层细节的团队。
socket.io
📡 Ably
托管式实时消息平台。按消息量计费,适合不想自建 WebSocket 基础设施。
ably.com
🌊 Pusher
老牌托管式 WebSocket 服务。Channels + Beams (推送)。
pusher.com
🔄 Supabase Realtime
PostgreSQL 变更监听 → WebSocket 推送。数据库行变更自动推送客户端。
supabase.com/realtime
☁️ Cloudflare Durable Objects
边缘计算单例,天然适合 WebSocket + 协作状态管理。
developers.cloudflare.com
⚡ µWebSockets.js
极致性能的 WebSocket 实现,比 ws 快 10x+。适合大规模场景。
GitHub
📝 PartyKit
基于 Durable Objects 的实时协作平台,Yjs 原生支持。
partykit.io
🚄 Fastify WebSocket
Fastify 框架的 WebSocket 插件,轻量高效。
GitHub

本站相关

外部资源