⚡ 多人协同编辑系统

基于 CRDT/OT 算法的实时文档协同编辑技术方案与 API 接口设计

Version 2.0 | 2026 年 3 月

1. 项目概述

🎯 项目目标:构建一个高可用、低延迟、支持大规模并发的多人实时协同编辑系统,实现类似 Google Docs、腾讯文档的协作体验。

1.1 核心特性

1.2 技术栈选型

前端技术

React/Vue 3TypeScriptProseMirrorYjsWebSocket

后端技术

Node.js/GoWebSocketRedisPostgreSQLMongoDB

基础设施

DockerKubernetesNginxPrometheusGrafana

算法库

Yjs(CRDT)AutomergeOT.jsShareDB

2. 需求分析

2.1 功能性需求

需求类别 详细描述 优先级
文档编辑 支持富文本/Markdown 编辑,实时保存和同步 P0
多人协作 支持 100+ 人同时在线编辑同一文档 P0
实时通信 编辑操作延迟 < 100ms,光标同步延迟 < 200ms P0
冲突处理 自动解决并发编辑冲突,无数据丢失 P0
权限控制 支持查看、评论、编辑、管理等权限级别 P1
版本管理 自动版本快照,支持历史版本对比和回滚 P1
离线支持 断网可编辑,重连后自动同步 P2

2.2 非功能性需求

⚡ 性能指标:
  • 单文档支持最大并发用户数:≥ 100 人
  • 编辑操作端到端延迟:< 100ms (P95)
  • 系统可用性:≥ 99.9%
  • 消息吞吐量:≥ 10,000 ops/sec
  • 文档加载时间:< 1s (P95)

3. 系统架构设计

3.1 整体架构图

┌─────────────────────────────────────────────────────────────────────────┐
│                            Client Layer                                  │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐                  │
│  │   Web App    │  │  Mobile App  │  │ Desktop App  │                  │
│  │  (React/Vue) │  │ (React Native)│ │  (Electron)  │                  │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘                  │
│         │                  │                  │                          │
│         └──────────────────┼──────────────────┘                          │
│                         WebSocket                                        │
└────────────────────────────┼─────────────────────────────────────────────┘
                             │
┌────────────────────────────▼─────────────────────────────────────────────┐
│                          Gateway Layer                                   │
│  ┌──────────────────────────────────────────────────────────────────┐   │
│  │                      Load Balancer (Nginx)                        │   │
│  └────────────────────────────┬─────────────────────────────────────┘   │
│                               │                                          │
│  ┌────────────────────────────▼─────────────────────────────────────┐   │
│  │                   WebSocket Gateway Cluster                       │   │
│  │              (Connection Management & Routing)                    │   │
│  └────────────────────────────┬─────────────────────────────────────┘   │
└────────────────────────────┼─────────────────────────────────────────────┘
                             │
┌────────────────────────────▼─────────────────────────────────────────────┐
│                        Application Layer                                 │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐ │
│  │   Document   │  │   User &     │  │   Version    │  │   Comment    │ │
│  │   Service    │  │   Auth       │  │   Service    │  │   Service    │ │
│  │   Service    │  │   Service    │  │              │  │              │ │
│  └──────────────┘  └──────────────┘  └──────────────┘  └──────────────┘ │
└────────────────────────────┬─────────────────────────────────────────────┘
                             │
┌────────────────────────────▼─────────────────────────────────────────────┐
│                         Message Queue                                    │
│                    (Redis Pub/Sub / Kafka)                               │
└────────────────────────────┬─────────────────────────────────────────────┘
                             │
┌────────────────────────────▼─────────────────────────────────────────────┐
│                          Data Layer                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐ │
│  │  PostgreSQL  │  │   MongoDB    │  │    Redis     │  │     OSS      │ │
│  │ (Metadata)   │  │ (Documents)  │  │  (Cache/     │  │  (Attachments)│ │
│  │              │  │   (CRDT)     │  │   Session)   │  │              │ │
│  └──────────────┘  └──────────────┘  └──────────────┘  └──────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
                

3.2 核心组件说明

3.2.1 客户端层 (Client Layer)

3.2.2 网关层 (Gateway Layer)

3.2.3 应用层 (Application Layer)

3.2.4 数据层 (Data Layer)

4. 冲突解决算法

💡 算法选择:本系统采用 CRDT (Conflict-free Replicated Data Types) 作为主要冲突解决算法,相比 OT 算法具有更好的去中心化特性和离线支持能力。

4.1 CRDT vs OT 对比

特性 CRDT OT (Operational Transformation)
一致性保证 强一致性 (数学证明) 依赖中心服务器转换
网络要求 支持 P2P、离线操作 需要中心服务器
实现复杂度 中等 较高
内存占用 较高 (需存储元数据) 较低
适用场景 分布式、离线优先应用 中心化实时协作

4.2 Yjs CRDT 实现方案

4.2.1 核心数据结构

// Yjs 核心数据类型
import { Y } from 'yjs'

// 文档类型定义
interface DocumentContent {
  content: Y.Text          // 富文本内容
  cursors: Y.Map<Cursor>   // 用户光标位置
  awareness: Y.Map<Awareness> // 用户状态感知
  metadata: Y.Map<any>     // 文档元数据
}

// 光标信息结构
interface Cursor {
  userId: string
  userName: string
  color: string
  position: number
  selection: { start: number, end: number } | null
  timestamp: number
}

4.2.2 操作流程

1 用户在本地编辑器执行操作 (插入/删除文本)
2 Yjs 捕获操作并生成本地 Update (二进制增量)
3 Update 通过 WebSocket 发送到服务器
4 服务器广播 Update 给所有连接的客户端
5 各客户端应用 Update,CRDT 保证最终一致性
6 异步持久化到数据库 (防丢失)

4.2.3 冲突解决示例

// 场景:两个用户同时在位置 5 插入不同文本
// 用户 A 插入 "Hello",用户 B 插入 "World"

// Yjs 会自动为每个操作分配唯一的 Lamport 时间戳和客户端 ID
// 根据 ID 和时间戳确定操作顺序,保证所有客户端结果一致

const ydoc = new Y.Doc()
const ytext = ydoc.getText('content')

// 用户 A 的操作
ytext.insert(5, 'Hello', clientA_ID)

// 用户 B 的操作 (同时发生)
ytext.insert(5, 'World', clientB_ID)

// CRDT 会根据客户端 ID 字典序决定顺序
// 假设 clientA_ID < clientB_ID,则结果为 "...HelloWorld..."
// 所有客户端都会得到相同的结果

4.3 Awareness 协议 (光标感知)

// 使用 Yjs Awareness 实现光标和状态同步
import { Awareness } from 'y-protocols/awareness'

const awareness = new Awareness(ydoc)

// 设置本地用户状态
awareness.setLocalStateField('user', {
  name: '张三',
  color: '#FF6B6B',
  cursorPosition: 125,
  selection: { start: 120, end: 130 }
})

// 监听其他用户状态变化
awareness.on('change', ({ added, updated, removed }) => {
  added.forEach(id => {
    const state = awareness.getState(id)
    showRemoteCursor(id, state.user)
  })
  updated.forEach(id => {
    const state = awareness.getState(id)
    updateRemoteCursor(id, state.user)
  })
  removed.forEach(id => {
    removeRemoteCursor(id)
  })
})

5. 数据模型设计

5.1 数据库 ER 图

┌─────────────────┐       ┌─────────────────┐       ┌─────────────────┐
│     Users       │       │   Documents     │       │  Doc_Versions   │
├─────────────────┤       ├─────────────────┤       ├─────────────────┤
│ id (PK)         │       │ id (PK)         │       │ id (PK)         │
│ username        │◄──────│ owner_id (FK)   │──────►│ doc_id (FK)     │
│ email           │       │ title           │       │ version_number  │
│ password_hash   │       │ content (Yjs)   │       │ content_snapshot│
│ avatar_url      │       │ created_at      │       │ created_by (FK) │
│ created_at      │       │ updated_at      │       │ created_at      │
└─────────────────┘       │ is_deleted      │       │ change_summary  │
         │                └─────────────────┘       └─────────────────┘
         │                         │
         │                ┌────────▼────────┐       ┌─────────────────┐
         │                │  Permissions    │       │  Operation_Log  │
         │                ├─────────────────┤       ├─────────────────┤
         │                │ id (PK)         │       │ id (PK)         │
         └───────────────►│ doc_id (FK)     │       │ doc_id (FK)     │
                          │ user_id (FK)    │       │ user_id (FK)    │
                          │ role            │       │ operation_type  │
                          │ granted_at      │       │ operation_data  │
                          │ granted_by (FK) │       │ timestamp       │
                          └─────────────────┘       │ vector_clock    │
                                                    └─────────────────┘
                

5.2 核心表结构

5.2.1 Users 表

字段名 类型 约束 说明
id UUID PRIMARY KEY 用户唯一标识
username VARCHAR(50) UNIQUE, NOT NULL 用户名
email VARCHAR(255) UNIQUE, NOT NULL 邮箱地址
password_hash VARCHAR(255) NOT NULL 密码哈希 (bcrypt)
avatar_url VARCHAR(500) NULL 头像 URL
created_at TIMESTAMP DEFAULT NOW() 创建时间

5.2.2 Documents 表

字段名 类型 约束 说明
id UUID PRIMARY KEY 文档唯一标识
owner_id UUID FK → Users.id 文档所有者
title VARCHAR(500) NOT NULL 文档标题
content_state BYTEA NULL Yjs 二进制状态 (最新)
created_at TIMESTAMP DEFAULT NOW() 创建时间
updated_at TIMESTAMP DEFAULT NOW() 最后更新时间
is_deleted BOOLEAN DEFAULT FALSE 软删除标记

5.2.3 Permissions 表

字段名 类型 约束 说明
id UUID PRIMARY KEY 权限记录 ID
doc_id UUID FK → Documents.id 关联文档
user_id UUID FK → Users.id 被授权用户
role ENUM NOT NULL 角色:viewer/commenter/editor/admin
granted_at TIMESTAMP DEFAULT NOW() 授权时间
granted_by UUID FK → Users.id 授权人

5.2.4 Operation_Log 表 (MongoDB)

{
  "_id": ObjectId,
  "doc_id": UUID,
  "user_id": UUID,
  "operation_type": "insert" | "delete" | "update",
  "operation_data": {
    "insert": { "position": Number, "content": String },
    "delete": { "position": Number, "length": Number }
  },
  "timestamp": ISODate,
  "vector_clock": { "client_id": Number },
  "sequence_num": Number,
  "yjs_update": BinData  // Yjs 增量更新二进制
}

6. API 接口设计

6.1 RESTful API 规范

📋 通用规范:
  • 基础路径:/api/v1
  • 认证方式:JWT Token (Authorization: Bearer <token>)
  • 响应格式:JSON
  • 字符编码:UTF-8
  • 日期格式:ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ)

6.2 认证接口

POST /auth/register
用户注册
// Request Body
{
  "username": "zhangsan",
  "email": "zhangsan@example.com",
  "password": "SecurePass123!"
}

// Response 201 Created
{
  "code": 201,
  "message": "注册成功",
  "data": {
    "userId": "550e8400-e29b-41d4-a716-446655440000",
    "username": "zhangsan",
    "email": "zhangsan@example.com"
  }
}
POST /auth/login
用户登录
// Request Body
{
  "email": "zhangsan@example.com",
  "password": "SecurePass123!"
}

// Response 200 OK
{
  "code": 200,
  "message": "登录成功",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
    "expiresIn": 7200,
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "username": "zhangsan",
      "email": "zhangsan@example.com",
      "avatarUrl": "https://oss.example.com/avatars/xxx.png"
    }
  }
}
POST /auth/refresh
刷新 Access Token
// Request Headers
Authorization: Bearer <refreshToken>

// Response 200 OK
{
  "code": 200,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expiresIn": 7200
  }
}

6.3 文档管理接口

POST /documents
创建文档
// Request Headers
Authorization: Bearer <accessToken>

// Request Body
{
  "title": "产品需求文档",
  "content": "",  // 可选,初始内容
  "type": "rich_text"  // rich_text | markdown
}

// Response 201 Created
{
  "code": 201,
  "message": "文档创建成功",
  "data": {
    "id": "doc-550e8400-e29b-41d4-a716-446655440001",
    "title": "产品需求文档",
    "ownerId": "550e8400-e29b-41d4-a716-446655440000",
    "createdAt": "2026-03-12T10:30:00.000Z",
    "updatedAt": "2026-03-12T10:30:00.000Z"
  }
}
GET /documents/{documentId}
获取文档详情
// Request Headers
Authorization: Bearer <accessToken>

// Response 200 OK
{
  "code": 200,
  "data": {
    "id": "doc-550e8400-e29b-41d4-a716-446655440001",
    "title": "产品需求文档",
    "content": "<p>文档内容...</p>",
    "type": "rich_text",
    "owner": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "username": "zhangsan",
      "avatarUrl": "https://oss.example.com/avatars/xxx.png"
    },
    "permissions": {
      "canEdit": true,
      "canComment": true,
      "canShare": true,
      "canDelete": false
    },
    "collaborators": [
      {
        "userId": "user-001",
        "username": "lisi",
        "role": "editor",
        "isOnline": true
      }
    ],
    "createdAt": "2026-03-12T10:30:00.000Z",
    "updatedAt": "2026-03-12T15:45:00.000Z"
  }
}
PUT /documents/{documentId}
更新文档元数据
// Request Headers
Authorization: Bearer <accessToken>

// Request Body
{
  "title": "产品需求文档 v2.0"
}

// Response 200 OK
{
  "code": 200,
  "message": "更新成功",
  "data": {
    "id": "doc-550e8400-e29b-41d4-a716-446655440001",
    "title": "产品需求文档 v2.0",
    "updatedAt": "2026-03-12T16:00:00.000Z"
  }
}
DELETE /documents/{documentId}
删除文档
// Request Headers
Authorization: Bearer <accessToken>

// Response 200 OK
{
  "code": 200,
  "message": "文档已删除"
}
GET /documents
文档列表 (支持分页和筛选)
// Query Parameters
page=1&limit=20&sortBy=updatedAt&order=desc&keyword=PRD

// Response 200 OK
{
  "code": 200,
  "data": {
    "list": [
      {
        "id": "doc-001",
        "title": "产品需求文档",
        "owner": { "username": "zhangsan" },
        "updatedAt": "2026-03-12T15:45:00.000Z",
        "myPermission": "editor"
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 156,
      "totalPages": 8
    }
  }
}

6.4 权限管理接口

POST /documents/{documentId}/permissions
添加文档权限
// Request Headers
Authorization: Bearer <accessToken>

// Request Body
{
  "userId": "user-002",
  "role": "editor",  // viewer | commenter | editor | admin
  "expireAt": "2026-04-12T00:00:00.000Z"  // 可选,过期时间
}

// Response 201 Created
{
  "code": 201,
  "message": "授权成功",
  "data": {
    "permissionId": "perm-001",
    "userId": "user-002",
    "role": "editor",
    "grantedAt": "2026-03-12T16:00:00.000Z"
  }
}
GET /documents/{documentId}/permissions
获取文档权限列表
// Response 200 OK
{
  "code": 200,
  "data": [
    {
      "permissionId": "perm-001",
      "user": {
        "id": "user-002",
        "username": "lisi",
        "avatarUrl": "https://oss.example.com/avatars/yyy.png"
      },
      "role": "editor",
      "grantedBy": { "username": "zhangsan" },
      "grantedAt": "2026-03-12T16:00:00.000Z"
    }
  ]
}
DELETE /documents/{documentId}/permissions/{permissionId}
撤销文档权限
// Response 200 OK
{
  "code": 200,
  "message": "权限已撤销"
}

6.5 版本管理接口

GET /documents/{documentId}/versions
获取文档版本历史
// Query Parameters
page=1&limit=50

// Response 200 OK
{
  "code": 200,
  "data": {
    "list": [
      {
        "versionId": "ver-050",
        "versionNumber": 50,
        "createdBy": {
          "id": "user-001",
          "username": "zhangsan"
        },
        "createdAt": "2026-03-12T15:45:00.000Z",
        "changeSummary": "更新了第三章内容",
        "size": 15234
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 50,
      "total": 50
    }
  }
}
GET /documents/{documentId}/versions/{versionId}
获取指定版本内容
// Response 200 OK
{
  "code": 200,
  "data": {
    "versionId": "ver-050",
    "versionNumber": 50,
    "content": "<p>历史版本内容...</p>",
    "createdAt": "2026-03-12T15:45:00.000Z",
    "createdBy": { "username": "zhangsan" }
  }
}
POST /documents/{documentId}/versions/{versionId}/rollback
回滚到指定版本
// Request Headers
Authorization: Bearer <accessToken>

// Request Body (可选)
{
  "comment": "回滚到稳定版本"
}

// Response 200 OK
{
  "code": 200,
  "message": "回滚成功",
  "data": {
    "newVersionId": "ver-051",
    "versionNumber": 51,
    "createdAt": "2026-03-12T16:30:00.000Z"
  }
}

6.6 评论接口

POST /documents/{documentId}/comments
添加评论
// Request Headers
Authorization: Bearer <accessToken>

// Request Body
{
  "content": "这段描述需要更详细一些",
  "range": {
    "start": 120,
    "end": 150
  },
  "parentId": "comment-001",  // 可选,回复评论时使用
  "mentions": ["user-002", "user-003"]  // @提及的用户
}

// Response 201 Created
{
  "code": 201,
  "data": {
    "commentId": "comment-002",
    "content": "这段描述需要更详细一些",
    "author": {
      "id": "user-001",
      "username": "zhangsan"
    },
    "createdAt": "2026-03-12T16:00:00.000Z",
    "replies": []
  }
}
GET /documents/{documentId}/comments
获取评论列表
// Response 200 OK
{
  "code": 200,
  "data": [
    {
      "commentId": "comment-001",
      "content": "整体结构不错",
      "author": {
        "id": "user-002",
        "username": "lisi"
      },
      "range": { "start": 0, "end": 50 },
      "createdAt": "2026-03-12T14:00:00.000Z",
      "resolved": false,
      "resolvedBy": null,
      "replies": [
        {
          "commentId": "comment-002",
          "content": "谢谢反馈!",
          "author": { "username": "zhangsan" },
          "createdAt": "2026-03-12T14:30:00.000Z"
        }
      ]
    }
  ]
}
PUT /documents/{documentId}/comments/{commentId}/resolve
解决评论
// Response 200 OK
{
  "code": 200,
  "message": "评论已解决",
  "data": {
    "commentId": "comment-001",
    "resolved": true,
    "resolvedBy": { "username": "zhangsan" },
    "resolvedAt": "2026-03-12T16:00:00.000Z"
  }
}

7. WebSocket 协议设计

7.1 连接建立

WS wss://api.example.com/ws/v1/collaborate?token=<accessToken>&documentId=<docId>
建立协同编辑 WebSocket 连接
// 连接参数
token: JWT Access Token (必需)
documentId: 文档 ID (必需)
protocol: y-websocket (协议版本)

// 成功连接响应
{
  "type": "connected",
  "clientId": "client-abc123",
  "documentId": "doc-001",
  "stateVector": Uint8Array,  // 当前文档状态向量
  "collaborators": [
    {
      "userId": "user-001",
      "userName": "zhangsan",
      "color": "#FF6B6B",
      "cursorPosition": 125
    }
  ]
}

// 错误响应
{
  "type": "error",
  "code": "AUTH_FAILED",
  "message": "Token 无效或已过期"
}

7.2 消息类型定义

消息类型 方向 说明
sync-step1 Client → Server 客户端发起同步请求,携带状态向量
sync-step2 Server → Client 服务器返回缺失的更新数据
update 双向 Yjs 增量更新 (二进制)
awareness 双向 光标位置和用户状态更新
cursor-broadcast Server → Client 广播其他用户的光标变化
user-join Server → Client 新用户加入通知
user-leave Server → Client 用户离开通知
ping/pong 双向 心跳保活 (30s 间隔)

7.3 消息格式示例

7.3.1 Yjs Update 消息

// 客户端发送编辑操作
{
  "type": "update",
  "data": Uint8Array,  // Yjs 编码的二进制更新
  "clientId": "client-abc123",
  "timestamp": 1710259200000
}

// 服务器广播给其他客户端
{
  "type": "update",
  "data": Uint8Array,
  "originClientId": "client-abc123",
  "timestamp": 1710259200000
}

7.3.2 Awareness 消息

// 客户端上报状态
{
  "type": "awareness",
  "clientId": "client-abc123",
  "state": {
    "user": {
      "userId": "user-001",
      "userName": "zhangsan",
      "color": "#FF6B6B",
      "avatarUrl": "https://oss.example.com/avatars/xxx.png"
    },
    "cursor": {
      "position": 125,
      "selection": {
        "start": 120,
        "end": 130
      }
    },
    "status": "editing"  // editing | viewing | away
  }
}

// 服务器广播状态变化
{
  "type": "awareness",
  "changes": {
    "added": [],
    "updated": ["client-abc123"],
    "removed": []
  },
  "states": {
    "client-abc123": { ... }  // 完整状态
  }
}

7.3.3 用户加入/离开消息

// 用户加入
{
  "type": "user-join",
  "user": {
    "userId": "user-002",
    "userName": "lisi",
    "color": "#4ECDC4",
    "avatarUrl": "https://oss.example.com/avatars/yyy.png"
  }
}

// 用户离开
{
  "type": "user-leave",
  "userId": "user-002",
  "reason": "disconnect"  // disconnect | timeout | kicked
}

7.4 心跳机制

// 客户端每 30 秒发送 ping
{
  "type": "ping",
  "timestamp": 1710259200000
}

// 服务器响应 pong
{
  "type": "pong",
  "timestamp": 1710259200000,
  "serverTime": 1710259200050
}

// 90 秒无活动自动断开连接

7.5 断线重连机制

🔄 重连策略:
  • 指数退避:重连间隔 = min(1000 * 2^retryCount, 30000) ms
  • 最大重试:最多重试 10 次,之后提示用户手动重连
  • 状态恢复:重连后自动请求 Sync 步骤,拉取缺失的更新
  • 离线队列:断网期间的操作存入本地队列,重连后按序发送

8. 安全设计

8.1 认证与授权

8.1.1 JWT Token 配置

{
  "algorithm": "RS256",
  "accessTokenExpiry": "2h",
  "refreshTokenExpiry": "7d",
  "issuer": "collab-system",
  "audience": "collab-client",
  "claims": {
    "userId": "user-001",
    "email": "user@example.com",
    "roles": ["user"],
    "permissions": {
      "doc-001": "editor",
      "doc-002": "viewer"
    }
  }
}

8.1.2 权限校验流程

1 客户端请求携带 JWT Token
2 网关层验证 Token 签名和有效期
3 解析 Token 中的用户信息和权限
4 服务层校验用户对资源的访问权限
5 权限不足返回 403 Forbidden

8.2 数据安全

8.2.1 传输加密

8.2.2 存储加密

8.3 防护措施

威胁类型 防护措施
XSS 攻击 输入过滤、输出编码、CSP 策略
CSRF 攻击 CSRF Token、SameSite Cookie
SQL 注入 参数化查询、ORM 框架
DDoS 攻击 限流、CDN、WAF
暴力破解 登录失败次数限制、验证码
会话劫持 Token 绑定 IP、定期轮换

8.4 审计日志

{
  "logId": "log-001",
  "timestamp": "2026-03-12T16:00:00.000Z",
  "userId": "user-001",
  "action": "document.update",
  "resource": "doc-001",
  "ipAddress": "192.168.1.100",
  "userAgent": "Mozilla/5.0...",
  "requestId": "req-abc123",
  "status": "success",
  "metadata": {
    "changeCount": 5,
    "duration": 120
  }
}

9. 性能优化

9.1 延迟优化

9.1.1 消息压缩

// 使用 gzip 压缩 Yjs Update 消息
import { gzip, ungzip } from 'pako'

// 发送前压缩
const compressed = gzip(updateData)
ws.send(compressed)

// 接收后解压
ws.onmessage = async (event) => {
  const data = await event.arrayBuffer()
  const decompressed = ungzip(data)
  applyUpdate(decompressed)
}

// 压缩率:通常可减少 60-80% 的数据量

9.1.2 增量同步优化

9.2 服务端优化

9.2.1 连接复用

🔧 连接池设计:
  • 单文档用户共享同一个 Yjs Document 实例
  • 减少内存占用和 GC 压力
  • 使用引用计数管理生命周期

9.2.2 批量处理

// 合并短时间内的多个操作
const BATCH_INTERVAL = 50 // ms
let batchQueue = []
let batchTimer = null

function enqueueOperation(op) {
  batchQueue.push(op)
  if (!batchTimer) {
    batchTimer = setTimeout(flushBatch, BATCH_INTERVAL)
  }
}

function flushBatch() {
  const mergedUpdate = mergeUpdates(batchQueue)
  broadcast(mergedUpdate)
  batchQueue = []
  batchTimer = null
}

9.3 客户端优化

9.3.1 虚拟滚动

9.3.2 防抖节流

// 光标更新使用节流 (100ms)
const throttledSendCursor = throttle(sendCursor, 100)

// 自动保存使用防抖 (2s)
const debouncedSave = debounce(saveToStorage, 2000)

editor.on('cursorChange', throttledSendCursor)
editor.on('contentChange', debouncedSave)

9.4 监控指标

指标名称 目标值 监控方式
编辑操作延迟 (P95) < 100ms Prometheus + Grafana
WebSocket 连接成功率 > 99.5% 自定义埋点
消息丢失率 < 0.01% 序列号校验
服务端 CPU 使用率 < 70% Node.js Metrics
内存使用率 < 80% heapdump 分析
数据库查询延迟 < 50ms Slow Query Log

10. 部署方案

10.1 容器化部署

10.1.1 Dockerfile 示例

# 多阶段构建
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./

ENV NODE_ENV=production
EXPOSE 3000

CMD ["node", "dist/server.js"]

10.1.2 Docker Compose

version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:pass@db:5432/collab
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis
    deploy:
      replicas: 3

  db:
    image: postgres:15
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=collab
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass

  redis:
    image: redis:7-alpine
    volumes:
      - redisdata:/data

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - app

volumes:
  pgdata:
  redisdata:

10.2 Kubernetes 部署

10.2.1 Deployment 配置

apiVersion: apps/v1
kind: Deployment
metadata:
  name: collab-app
spec:
  replicas: 5
  selector:
    matchLabels:
      app: collab
  template:
    metadata:
      labels:
        app: collab
    spec:
      containers:
      - name: app
        image: collab-app:latest
        ports:
        - containerPort: 3000
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: url
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5

10.3 水平扩展策略

📈 扩展方案:
  • WebSocket 网关层:基于文档 ID 一致性哈希路由
  • Redis Cluster:Pub/Sub 跨节点消息广播
  • 数据库读写分离:主库写,从库读
  • HPA 自动扩缩容:基于 CPU/内存/连接数指标

10.4 灾备方案

故障类型 应对策略 RTO/RPO
单节点故障 K8s 自动重启 + 健康检查 RTO < 30s
机房故障 多可用区部署 + DNS 切换 RTO < 5min
数据库故障 主从切换 + 数据复制 RPO < 1min
数据误删 每日备份 + 版本回滚 RPO < 24h