01 · 架构心法:决策框架与造轮子的时机

“架构不是画框图,是在约束下做决策。“


开场:一次”正确”的失败

小林:我设计了一个系统,用了微服务、消息队列、分布式缓存、分库分表。技术选型都是主流方案。但上线半年后,维护成本极高,团队天天救火。我哪里错了?

老陈:你的系统有多少用户?

小林:……日活 5000。

老陈:团队几个人?

小林:4 个。

老陈:这就是错误所在。

小林:可是这些技术都是”最佳实践”啊。

老陈:脱离约束的’最佳实践’就是灾难。 我问你:

  • 日活 5000,需要分库分表吗?不需要。单表几百万行,MySQL 轻松搞定。
  • 4 个人的团队,能维护 20 个微服务吗?不能。
  • 你需要消息队列吗?如果 QPS 只有几十,不需要。

你引入的每一个组件,都要付出:学习成本、运维成本、调试成本、故障概率。

日活 5000 的系统,一个单体 + 一个 MySQL + 一个 Redis,就够了。

小林:那我什么时候该用微服务?

老陈:这就是今天的核心问题。我们要建立一个判断框架,而不是记住”该用什么”。


第一部分:架构的本质

架构 = 决策 + 后果

架构师的核心工作不是"设计系统",而是"做决策"

一个决策包含三部分:
  ① 约束(我面对什么)
  ② 选择(我选什么)
  ③ 理由(为什么这么选,以及我放弃了什么)

★ 判断一个人是否真的懂架构,看他能不能说出”我放弃了什么”。

只会说”我用微服务因为它好”的人,不懂架构。 能说”我用微服务,代价是运维复杂度和调试难度上升,但我们团队有 20 个人,能扛住”的人,才懂。

架构的三个层次

┌──────────────────────────────────────────┐
│  业务架构                                  │
│  · 业务域怎么划分                           │
│  · 领域模型是什么                           │
│  · 业务流程怎么串                           │
│  ★ 决定"系统长什么样"                       │
├──────────────────────────────────────────┤
│  应用架构                                  │
│  · 服务怎么拆分                             │
│  · 服务怎么通信                             │
│  · 数据怎么流转                             │
│  ★ 决定"代码怎么写"                         │
├──────────────────────────────────────────┤
│  技术架构                                  │
│  · 用什么中间件                             │
│  · 用什么存储                               │
│  · 怎么部署                                 │
│  ★ 决定"用什么工具"                         │
└──────────────────────────────────────────┘

★ 顺序很重要:业务架构 → 应用架构 → 技术架构
★ 反过来做(先选技术,再套业务)是本末倒置

老陈:很多人一上来就问”用什么数据库”、“用不用 Kafka”。

这些是技术架构的问题,应该最后才决定。

先搞清楚业务:

  • 有哪些实体?它们的关系是什么?
  • 核心流程是什么?
  • 哪些地方是瓶颈?
  • 数据的一致性要求是什么?

答案清楚了,技术选型几乎是唯一的。

架构的质量标准

不是”用了多少先进技术”,而是:

标准含义反面
可理解性新人多久能看懂过度抽象、“魔法”太多
可修改性改一个需求要动多少地方牵一发动全身
可测试性能不能独立测试必须起整个环境
可运维性出问题多久能定位日志缺失、无监控
可扩展性加功能/加机器容易吗需要重构

★ 最重要的:可理解性。

一个只有你能维护的系统,是最糟糕的架构。

—— 哪怕它”技术上先进”。


第二部分:造轮子的时机判断

★ 核心判断框架

这是本章最重要的内容。

问自己四个问题,按顺序:

问题 1:现成方案能满足需求吗?

调研所有现成的方案,问:
  · 功能满足吗?
  · 性能满足吗?
  · 能满足未来的需求吗?

★ 如果答案是"能" → 不要造
★ 即使它不是"最优"的

关键:接受”不完美”。

现成方案能满足 90% 的需求
自研能满足 100%,但要 3 个月

★ 除非那 10% 是核心业务,否则不值得

问题 2:这是你的核心竞争力吗?

判断标准:
  如果这个能力做到极致,能带来商业价值吗?

例子:
  · 电商的推荐算法       → ★ 核心竞争力,值得自研
  · 电商的 Web 框架      → 不是,用 gin 就好
  · 数据库的存储引擎     → 看情况(如果是做数据库的公司,就是)
  · 日志库              → 不是,用 zap

★ 判断标准:这个能力是”业务差异化的来源”吗?

问题 3:你有能力长期维护吗?

自研不是"写出来"就完了,是"维护到系统下线"。

问:
  · 团队有人懂这个领域吗?
  · 有人能 24 小时 oncall 处理故障吗?
  · 半年后新人能接手吗?
  · 有测试覆盖吗?
  · 有文档吗?

★ 如果答案是"不确定" → 不要造

这是最常被忽略的问题。 很多人造轮子时很兴奋,半年后没人维护,变成”技术债”。

问题 4:成本收益比合理吗?

成本:
  · 开发时间(N 人月 × 人力成本)
  · 维护成本(长期)
  · 机会成本(这些时间可以用来做什么)

收益:
  · 性能提升?
  · 成本降低(比如省下 License 费用)?
  · 能力提升(团队技术积累)?
  · 业务价值?

★ 只有当收益明显大于成本时才值得

决策树

要引入一个新技术 / 造一个轮子
    │
    ├─ Q1: 有现成方案吗?
    │    ├─ 没有 → 考虑造(但先确认需求真实存在)
    │    └─ 有 ↓
    │
    ├─ Q2: 现成方案满足需求吗?
    │    ├─ 满足 → ★ 用现成的,结束
    │    └─ 不满足 ↓
    │
    ├─ Q3: 差距在哪?(性能?功能?成本?)
    │    ├─ 可以通过配置/扩展解决 → 用现成的 + 扩展
    │    └─ 不能 ↓
    │
    ├─ Q4: 这是核心竞争力吗?
    │    ├─ 不是 → ★ 考虑"换业务方案"而不是"造技术轮子"
    │    └─ 是 ↓
    │
    ├─ Q5: 有能力维护吗?
    │    ├─ 没有 → ★ 不要造
    │    └─ 有 ↓
    │
    └─ Q6: 成本收益比合理吗?
         ├─ 不合理 → 不要造
         └─ 合理 → 造,但要有 Plan B

案例分析:什么时候该自己写存储引擎

场景 A:做日志收集系统

需求:写多读少,数据按时间查询

现成方案:Elasticsearch
  · 功能满足 ✓
  · 性能满足 ✓
  · 但资源占用大(ES 很吃内存)

★ 结论:用 ES。除非你的日志量大到 ES 成本不可接受
  (比如每天 100TB,那可能要自研或者用 ClickHouse)

场景 B:做时序数据库产品

需求:高性能时序数据存储

现成方案:
  · InfluxDB(开源版不支持集群)
  · TimescaleDB(基于 PG,性能有限)
  · 但你做的是"时序数据库产品"

★ 结论:自研。因为存储引擎就是你的产品本身

场景 C:内部监控系统的存储

需求:存监控指标,保留 30 天

现成方案:Prometheus + Thanos / VictoriaMetrics
  · 功能满足 ✓
  · 性能好 ✓
  · 开源免费 ✓

★ 结论:用现成的。
  自研一个时序数据库至少 6 人月,而且做得还不如 Prometheus

第三部分:架构演进

三个阶段

阶段 1: 单体 (Monolith)
  ┌─────────────────┐
  │   所有功能        │
  │   ┌───┬───┐     │
  │   │用户│订单│ ... │     │
  │   └───┴───┘     │
  ├─────────────────┤
  │    单个数据库     │
  └─────────────────┘

  优点: 简单、开发快、易测试、易部署
  缺点: 耦合、无法独立扩展、技术栈单一
  适用: 团队 < 10 人,用户 < 100 万

阶段 2: 服务化 (SOA / 微服务)
  ┌──────┐ ┌──────┐ ┌──────┐
  │用户服务│ │订单服务│ │支付服务│
  └───┬──┘ └───┬──┘ └───┬──┘
      └────────┼────────┘
          ┌────▼────┐
          │ 服务治理  │
          └─────────┘

  优点: 独立开发/部署/扩展、技术栈自由
  缺点: 分布式复杂度、调试难、运维重
  适用: 团队 > 20 人,用户 > 1000 万

阶段 3: 中台 / 平台化
  业务前台(快速试错)
      ↓
  业务中台(复用能力)
      ↓
  技术中台(基础设施)

  优点: 复用、快速创新
  缺点: 中台本身可能变成瓶颈("中台病")
  适用: 大公司,多条业务线

★ 演进的原则:不要跳步

错误路径:
  创业公司 → 直接上微服务 → 团队 5 个人维护 20 个服务
  → 天天救火 → 产品做不出来 → 公司倒闭

正确路径:
  阶段 1: 单体(但要"模块化",为拆分做准备)
      ↓ 业务增长,团队扩大
  阶段 2: 拆分(先拆最独立的、最需要扩展的)
      ↓ 多条业务线
  阶段 3: 平台化

★ 关键:单体时要”模块化”

❌ 糟糕的单体:
  所有代码互相调用,没有边界
  → 拆不动

✅ 好的单体("模块化单体" Modular Monolith):
  ┌─────────────────────────┐
  │  module A │ module B │ C │
  │    ↓ 只能通过接口调用 ↓    │
  └─────────────────────────┘
  · 模块间通过明确的接口通信
  · 模块内高内聚
  · 模块间低耦合
  → 需要时能拆成独立服务

模块化单体的实践:

// 内部目录结构
internal/
├── user/          # 用户模块
│   ├── api.go     # 对外接口
│   ├── service.go # 业务逻辑
│   └── repo.go    # 数据访问
├── order/         # 订单模块
│   ├── api.go
│   └── ...
└── payment/
    └── ...
 
// 模块间只能通过 api.go 调用
// ★ 禁止直接访问其他模块的 repo 或内部结构

第四部分:技术选型的框架

四个维度

① 成熟度 (Maturity)
   · 有多少公司在用?
   · 社区活跃吗?
   · 文档完善吗?
   · ★ 出问题能搜到答案吗?

② 匹配度 (Fit)
   · 功能匹配需求吗?
   · 性能匹配吗?
   · 团队技能匹配吗?

③ 成本 (Cost)
   · License 费用?
   · 硬件成本?
   · 学习成本?
   · 运维成本?

④ 风险 (Risk)
   · 供应商锁定?
   · 技术会不会过时?
   · 出了问题有人支持吗?

选型矩阵

以”消息队列”为例:

方案成熟度匹配度成本风险适用
Kafka⭐⭐⭐⭐⭐高吞吐、持久化中(运维重)低★ 日志、流处理、大数据
RabbitMQ⭐⭐⭐⭐⭐功能丰富、灵活路由中低业务消息、任务队列
RocketMQ⭐⭐⭐⭐事务消息、顺序消息中中(国内生态好)电商、金融
Pulsar⭐⭐⭐存算分离、多租户中中(较新)多租户、云原生
Redis Stream⭐⭐⭐⭐轻量低低★ 小规模、简单场景
自研-完全匹配高高★ 几乎不要

★ 我的默认建议:

小流量(QPS < 1万):  Redis Stream 或 RabbitMQ
                    简单,运维成本低

中大规模(QPS 1万-100万): Kafka
                         生态最好,招人容易

需要事务消息:        RocketMQ

★ 几乎不要自研消息队列
  除非你就是做消息队列的

第五部分:许式伟的架构观

许式伟(七牛云创始人,Go 圈早期的布道者,《Go 语言编程》作者)的架构课有几个核心观点,值得单独讲。

观点 1:架构的第一步是”需求分析”

他的说法:
  "很多人做架构,上来就说'我要做一个分布式系统'。
   但你问他:你的数据规模多大?峰值 QPS 多少?
   他答不出来。
   
   ★ 架构的第一步不是画框图,是把需求数字化。"

需求要数字化到这个程度:

✗ "用户量很大"
✓ "日活 100 万,峰值 QPS 2 万,每秒写入 5000 条"

✗ "数据量很大"
✓ "每天新增 500GB,保留 6 个月,总共 90TB"

✗ "要求高可用"
✓ "可用性 99.95%,允许每月 22 分钟不可用"

✗ "要求低延迟"
✓ "P99 < 100ms,P999 < 500ms"

★ 有了这些数字,架构几乎是唯一确定的。

观点 2:少谈”战略”,多谈”问题”

他的说法:
  "架构师容易犯的错:一上来就讲'我们的架构要支持未来三年'。
   但你连明年的业务是什么都不知道。
   
   ★ 好的架构不是'预测未来',而是'拥抱变化'——
     让系统容易改,而不是试图一次设计对。"

这是”演进式架构”的思想:

❌ 过度设计:
  "未来可能需要多租户,现在就加上"
  "未来可能要换数据库,现在就抽象一层"
  → 90% 的"未来"不会发生
  → 但你付出了 100% 的复杂度

✅ 演进式设计:
  先解决当前问题,但保持"可修改性"
  · 模块化
  · 接口清晰
  · 测试覆盖
  → 需求变了,能改得动

观点 3:从”文件”到”分布式”的信息观

许式伟有一个很有意思的视角:

他把整个计算机体系看作"信息的组织和流动":

  单机时代: 信息存在"文件"里
           → 核心抽象是文件、目录、流式读写

  网络时代: 信息存在"服务端"里
           → 核心抽象是请求-响应、数据库

  分布式时代: 信息存在"分布式系统"里
             → 核心抽象是一致性、分区、副本

  云计算时代: 信息存在"云"上
             → 核心抽象是弹性、按需、多租户

★ 每一次范式转移,都是"信息的组织方式"变了
★ 架构师要理解当前范式,并预判下一个

思考题 ·【创造层】

你现在负责一个创业公司的技术架构。业务是在线协作文档(类似 Google Docs / 飞书文档)。

  • 团队 8 个工程师(5 后端 + 2 前端 + 1 运维)
  • 预期 1 年内日活 10 万,同时在线编辑 5000 人
  • 核心功能:多人实时协同编辑、历史版本、权限管理
  • 预算有限

请设计架构,说明:

  1. 服务怎么拆分(或不拆分)
  2. 实时协同用什么方案(OT 还是 CRDT?自研还是用库?)
  3. 存储怎么设计
  4. 你会避免哪些”看起来很美”的陷阱
参考答案

一、先算需求和约束

需求数字化

日活 10 万,假设:
  · 每人每天编辑 2 个文档
  · 每个文档平均编辑 30 分钟
  · 编辑期间每 3 秒产生一次操作

同时在线编辑峰值 5000 人:
  · 操作频率: 5000 / 3 ≈ 1700 ops/s
  · 峰值可能 3 倍: 5000 ops/s

数据量:
  · 假设每个文档平均 100KB
  · 每个用户 20 个文档 = 2MB
  · 10 万用户 = 200GB
  ★ 数据量不大

实时性要求:
  · 本地编辑响应: < 50ms(本地立即生效)
  · 其他协作者看到: < 500ms
  · ★ 注意这两个是不同的

约束

团队: 8 人(5 后端)
时间: 产品要尽快上线验证
预算: 有限

★ 关键约束:团队小、时间紧、预算少
  这决定了架构必须"简单"

二、服务拆分:不拆(或最小化拆分)

我的建议:模块化单体

❌ 一开始就拆微服务:
  用户服务、文档服务、协同服务、权限服务、
  版本服务、通知服务、搜索服务...
  
  → 8 个团队维护 7 个服务
  → 分布式调试、链路追踪、服务发现全都要
  → ★ 团队会死在基础设施上

✅ 模块化单体(起步):
  ┌────────────────────────────────────┐
  │  单个 Go 服务(模块化)              │
  │  ┌────────┬────────┬────────┐      │
  │  │ 文档模块 │ 协同模块 │ 权限模块 │      │
  │  └────────┴────────┴────────┘      │
  │  ┌────────┬────────┐               │
  │  │ 版本模块 │ 用户模块 │               │
  │  └────────┴────────┘               │
  ├────────────────────────────────────┤
  │  PostgreSQL + Redis                 │
  └────────────────────────────────────┘

为什么是单体:

理由说明
团队小5 个后端维护 1 个服务 vs 7 个服务,效率差 3-5 倍
数据量小200GB,单库完全够(甚至几年内都不用分库)
迭代速度单体部署快、调试快、测试快
业务未验证产品方向可能变,微服务会锁死架构

但必须模块化(为未来拆分做准备):

// 目录结构
internal/
├── document/       # 文档模块
│   ├── api.go      # 对外接口(其他模块只能调这个)
│   ├── service.go
│   └── repo.go
├── collab/         # 协同模块
│   ├── api.go
│   ├── ot.go       # OT 算法
│   └── hub.go      # WebSocket 连接管理
├── permission/
├── version/
└── user/
 
// ★ 规则:
//   1. 模块间只能通过 api.go 调用
//   2. 不允许直接访问其他模块的 repo
//   3. 模块间不共享数据库表(每个模块自己的表)

什么时候拆?

信号:
  ① 某个模块需要独立扩展(比如协同服务需要更多机器)
  ② 某个模块的变更频率远高于其他(独立部署能加快迭代)
  ③ 团队规模扩大到 15+ 人
  ④ 某个模块需要不同的技术栈

★ 在出现这些信号之前,不要拆

但有一个例外:协同服务(WebSocket)可以独立

理由:
  · WebSocket 是长连接,跟 HTTP 请求的资源模型完全不同
  · 长连接服务需要单独的水平扩展策略
  · 长连接的内存占用模式不同(每连接占用内存)
  · 重启策略不同(HTTP 服务可以随时重启,长连接要考虑重连)

★ 所以:拆成 2 个服务
  ① API 服务(HTTP,处理文档 CRUD、权限、版本)
  ② 协同服务(WebSocket,处理实时编辑)

  这是"按技术特性拆分",不是"按业务域拆分"
  ★ 这种拆分是合理的,因为拆分理由是"技术需求",不是"组织需求"

三、实时协同:OT 还是 CRDT?用库还是自研?

OT vs CRDT

OT (Operational Transformation)CRDT
原理操作在服务端变换后应用数据结构本身保证收敛
服务端需要中心化的变换逻辑可以 P2P
实现复杂度中等(变换函数难写对)高(数据结构复杂)
成熟度★ 高(Google Docs 用了 20 年)中(较新)
生态丰富较少
离线编辑困难★ 天然支持
性能(大文档)好可能膨胀

我的建议:用 OT,且用现成的库

理由:
  ① 你的场景是"在线协作",不是"离线优先"
     → OT 完全够用,CRDT 的优势(离线)用不上

  ② OT 生态成熟
     · Go: 有几个开源实现
     · JS: ot.js、sharedb 都很成熟

  ③ CRDT 的实现复杂度高得多
     · 8 个人团队自研 CRDT,风险太大
     · 而且 CRDT 的"自动合并"在某些场景会产生奇怪的结果

★ 绝对不要自研 OT 算法。

OT 的变换函数是出了名的难写对:
  · 两个并发操作,变换后要满足 TP1、TP2 性质
  · 边界情况极多(删除+插入+格式化的各种组合)
  · ★ 这是学术界研究了 30 年的问题

  自研的代价:
    · 至少 3-6 人月
    · 而且你做的不如成熟的库
    · 出 bug 了难查(并发时序问题)

★ 用现成的:
  · 前端: ot.js / Yjs(Yjs 是 CRDT,但很成熟)
  · 后端: 找个 Go 的 OT 库,或者用 Node 的 sharedb

如果一定要自研,建议用 Yjs(CRDT)

Yjs 是目前最成熟的协同库:
  · 支持离线编辑
  · 性能极好
  · 有 CRDT 的理论保证(不会出错)
  · 生态丰富(有 WebSocket provider、数据库持久化)

★ 用 Yjs 的话,后端只需要:
  · y-websocket server(Node.js,官方就有)
  · 或者用 Go 实现一个 Yjs 的 WebSocket 服务

  ★ 这样你不用实现协同算法,只需要"转发消息 + 持久化"

实时协同的架构

┌─────────┐  WebSocket   ┌──────────────┐
│ 浏览器 A │◄────────────►│              │
└─────────┘              │   协同服务     │
┌─────────┐  WebSocket   │              │
│ 浏览器 B │◄────────────►│  ┌────────┐  │
└─────────┘              │  │房间管理  │  │
                         │  ├────────┤  │
                         │  │OT 变换  │  │
                         │  ├────────┤  │
                         │  │持久化   │  │
                         │  └────────┘  │
                         └──────┬───────┘
                                │
                         ┌──────▼───────┐
                         │  Redis Pub/Sub│  ← 多实例间广播
                         └──────────────┘
                                │
                         ┌──────▼───────┐
                         │  PostgreSQL   │  ← 定期保存快照
                         └──────────────┘

关键设计:

① 房间(Room)模型

一个文档 = 一个房间
房间里维护:
  · 当前在线的用户列表
  · 文档的当前内容(内存)
  · 操作历史(用于 OT 变换)
  · 版本号

② 多实例广播

协同服务会有多个实例(水平扩展)
用户 A 在实例 1,用户 B 在实例 2
→ 实例 1 收到操作,要广播给实例 2
→ ★ 用 Redis Pub/Sub

③ 持久化策略

不要每个操作都写数据库(太频繁)

策略:
  · 每 N 个操作 或 每 M 秒,保存一次快照到 PostgreSQL
  · 操作日志写 Redis(用于崩溃恢复)
  · ★ 权衡:崩溃时丢失多少操作?
     保存频率越高,丢失越少,但数据库压力越大
     建议:每 5 秒 或 每 100 个操作,取先到者

四、存储设计

数据模型

-- 用户
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(100),
    created_at TIMESTAMPTZ DEFAULT NOW()
);
 
-- 文档(元数据)
CREATE TABLE documents (
    id BIGSERIAL PRIMARY KEY,
    title VARCHAR(500),
    owner_id BIGINT REFERENCES users(id),
    -- ★ 当前内容(快照)
    content JSONB,          -- 或者存 Yjs 的二进制格式
    version BIGINT DEFAULT 0,
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ
);
 
-- 版本历史(★ 关键:不要存全量,存增量)
CREATE TABLE document_versions (
    id BIGSERIAL PRIMARY KEY,
    document_id BIGINT REFERENCES documents(id),
    version BIGINT,
    -- 增量(操作序列)
    operations JSONB,
    -- 或者:定期存全量快照 + 中间存增量
    is_snapshot BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMPTZ,
    created_by BIGINT
);
CREATE INDEX idx_doc_versions ON document_versions(document_id, version DESC);
 
-- 权限
CREATE TABLE document_permissions (
    document_id BIGINT,
    user_id BIGINT,
    role VARCHAR(20),   -- owner / editor / viewer
    PRIMARY KEY (document_id, user_id)
);
CREATE INDEX idx_perm_user ON document_permissions(user_id);

为什么用 PostgreSQL 而不是 MongoDB

很多人觉得"文档是 JSON,用 MongoDB 更合适"
★ 这是错的

理由:
  ① 协作文档有强一致性要求(权限、版本不能出错)
     → PostgreSQL 的 ACID 更可靠

  ② 你需要事务(比如"创建文档 + 设置权限"要原子)
     → MongoDB 的事务支持较晚、较弱

  ③ JSONB 类型足够灵活
     → 文档内容可以存 JSONB,还能建索引、做查询

  ④ 团队熟悉 SQL
     → 招人容易,运维简单

★ MongoDB 的优势(灵活 schema、水平扩展)在这个场景用不上

Redis 的用途

① 协同服务的 Pub/Sub(多实例广播)
② 文档内容的缓存(热文档)
③ 在线用户状态
④ 操作日志(崩溃恢复用,短期)

五、要避免的”看起来很美”的陷阱

陷阱 1:一开始就搞微服务

❌ "我们要支持未来百万用户,现在就上微服务"
   → 8 人团队维护 10 个服务
   → 80% 的时间在搞基础设施

✅ 模块化单体,需要时再拆

陷阱 2:自研协同算法

❌ "OT 算法看起来不难,我们自己写"
   → 3-6 人月
   → 边界情况永远修不完
   → ★ 而且这是学术界研究了 30 年的问题

✅ 用 Yjs 或成熟的 OT 库

陷阱 3:为每个操作写数据库

❌ "保证不丢失每一个操作"
   → 5000 ops/s 写数据库 → 数据库崩溃

✅ 批量保存快照 + Redis 存操作日志
   → 崩溃最多丢失 5 秒的操作(用户重连时会重新同步)

陷阱 4:用 Kubernetes(起步阶段)

❌ "K8s 是云原生标准,必须用"
   → 1 个运维维护 K8s 集群
   → 学习曲线陡峭
   → 出问题难排查

✅ 起步: Docker Compose 或者云厂商的托管服务
   → 等业务稳定、团队扩大后再上 K8s
   
   ★ 或者直接用云厂商的容器服务(比如阿里云 ACK Serverless)
     省去运维 K8s 本身的成本

陷阱 5:过度设计权限系统

❌ "我们要支持 RBAC + ABAC + 自定义角色 + 权限继承"
   → 设计 3 个月

✅ 起步: owner / editor / viewer 三个角色
   → 90% 的场景够用
   → 需要时再扩展

陷阱 6:忽略”文档很大”的情况

场景: 有人把 10MB 的文本粘贴进文档

❌ 没有限制 → 协同服务内存爆炸 → 所有用户受影响

✅ 限制:
   · 单文档大小上限(比如 5MB)
   · 单次操作大小上限
   · ★ 超过限制要有明确的错误提示

六、我的最终建议

架构总结

┌─────────────────────────────────────────────┐
│                 前端                          │
│   React + Yjs (CRDT)                        │
└──────────────────┬──────────────────────────┘
                   │ WSS
┌──────────────────▼──────────────────────────┐
│              协同服务 (Go)                    │
│   · WebSocket 连接管理                        │
│   · 房间管理                                  │
│   · Yjs 消息转发 + 持久化                     │
│   · ★ 不实现协同算法,用 Yjs                  │
│   ┌──────────────────┐                      │
│   │  Redis Pub/Sub   │  多实例广播            │
│   └──────────────────┘                      │
└──────────────────┬──────────────────────────┘
                   │ HTTP + WebSocket
┌──────────────────▼──────────────────────────┐
│              API 服务 (Go 单体)               │
│   ┌────────┬────────┬────────┬────────┐     │
│   │用户模块 │文档模块 │权限模块 │版本模块 │     │
│   └────────┴────────┴────────┴────────┘     │
└──────────────────┬──────────────────────────┘
                   │
        ┌──────────┴──────────┐
        ▼                     ▼
┌──────────────┐      ┌──────────────┐
│  PostgreSQL   │      │    Redis     │
│  · 用户       │      │  · Pub/Sub   │
│  · 文档元数据  │      │  · 缓存      │
│  · 版本历史   │      │  · 在线状态   │
│  · 权限       │      │              │
└──────────────┘      └──────────────┘

技术选型

组件选择理由
后端语言Go团队熟悉、并发好、部署简单
HTTP 框架gin 或 chi简单够用
数据库PostgreSQLACID、JSONB、团队熟悉
缓存Redis够用、简单
协同Yjs(前端)+ Go 转发★ 不自研算法
ORMsqlx(不是 GORM)简单、可控、性能可见
部署Docker Compose(起步)简单;后续可上云厂商 K8s
监控Prometheus + Grafana开源、标准

演进路径

第 1-3 月:  MVP
            · 单体服务
            · 基础协同(Yjs + 内存)
            · 简单的版本历史

第 4-6 月:  稳定性
            · 持久化优化(快照策略)
            · 权限细化
            · 监控告警

第 7-12 月: 扩展
            · 协同服务独立部署(如果需要)
            · 搜索功能(Elasticsearch)
            · 性能优化

★ 什么时候拆微服务?
   团队 > 15 人,或 某个模块需要独立扩展时

一句话总结

这个场景的核心矛盾是”野心”和”资源”。

  • 野心:实时协同、百万用户、完整功能
  • 资源:8 个人、有限预算、时间紧

解法是”聚焦”:

  1. 只拆该拆的(协同服务因为技术特性需要独立)
  2. 不自研不该自研的(协同算法用 Yjs)
  3. 不做不必要的基础设施(不上 K8s、不搞微服务全家桶)

把有限的资源,投入到”产品差异化”上,而不是”技术炫技”上。

这是架构师最重要的判断力——知道什么不该做,比知道该做什么更难。


小结:这一节你应该带走的东西

  1. 架构 = 在约束下做决策。判断一个人是否懂架构,看他能不能说出”我放弃了什么”。

  2. ★ 造轮子的四个判断:有现成方案吗?是核心竞争力吗?有能力维护吗?成本收益合理吗?

  3. 架构演进不要跳步:模块化单体 → 按需拆分 → 平台化。单体时要模块化,为拆分做准备。

  4. 技术选型的四个维度:成熟度、匹配度、成本、风险。

  5. 需求必须数字化——有了数字,架构几乎是唯一确定的。

  6. 好的架构不是”预测未来”,而是”拥抱变化”——保持可修改性,而不是一次设计对。

  7. 架构师最重要的判断力:知道什么不该做。


下一节

02 · 音视频系统:从采样到流媒体

最后一节。我们要把前面 7 章的所有知识,用在一个最”吃基础设施”的系统上。

老陈的预告:为什么直播延迟总是 3-10 秒,而不是 0.3 秒?

这不是技术做不到,是权衡的结果。 下一节你会理解这个权衡,并能自己设计一个直播系统。