关键决策

设计决策回顾

在整个系统的演进过程中,我们做了很多关键的技术决策。

每个决策都有其背景、权衡和结果。

决策 1:数据库 vs Redis

问题

最开始应该用什么存储计数?

方案对比

方案优点缺点适用场景
数据库实现简单,数据可靠,事务支持性能差,扩展性差小规模,低并发
Redis性极高,原子操作,扩展性好内存存储,需持久化大规模,高并发

决策

初期:使用数据库
- 用户量 < 1000
- 日均点赞 < 100
- 数据库完全够用

增长后:引入 Redis
- 用户量 > 10000
- 日均点赞 > 5000
- 数据库性能瓶颈

最终:Redis + 数据库
- Redis 处理读写
- 数据库持久化
- 各司其职

经验教训

1. 不要过早优化
   - 先用最简单的方案
   - 遇到问题再优化
   - 避免过度设计

2. 技术选型要匹配业务阶段
   - 初期:简单优先
   - 增长期:性能优先
   - 成熟期:稳定优先

3. 保持架构可演进
   - 预留优化空间
   - 设计替换接口
   - 支持灰度发布

决策 2:强一致 vs 最终一致

问题

计数系统需要多强的一致性?

方案对比

方案一致性性能复杂度适用场景
强一致立即一致金融交易
最终一致短暂延迟社交应用

决策

点赞场景分析:

实时性要求:
- 用户点赞后,看到计数 +1
- 允许短暂延迟(1-2 秒)
- 不影响用户体验

决策:最终一致

原因:
1. 性能要求高
2. 用户可容忍延迟
3. 实现相对简单
4. 成本较低

实现方案

1. 更新 Redis(立即返回)
2. 异步写数据库
3. 定期对账修正

延迟时间:< 1 秒
修正频率:每小时
不一致率:< 0.1%

经验教训

1. 一致性不是非黑即白
   - 不同场景有不同需求
   - 权衡成本和收益
   - 选择合适的一致性级别

2. 设计对账机制
   - 即使是最终一致
   - 也要保证最终会一致
   - 对账是最后的防线

3. 监控数据一致性
   - 实时监控不一致率
   - 超标告警
   - 及时修复

决策 3:单机 vs 分布式

问题

什么时候需要分布式?

方案对比

方案复杂度成本性能可用性
单机低(单点故障)
分布式

决策

演进路径:

阶段 1:单机部署
- 用户量 < 10,000
- 单台服务器足够
- 运维简单

阶段 2:主从部署
- 用户量:10,000 ~ 100,000
- 数据库主从
- Redis 主从
- 提高可用性

阶段 3:分布式部署
- 用户量 > 100,000
- 应用服务器集群
- Redis 集群
- 数据库集群
- 高可用

经验教训

1. 分布式不是银弹
   - 增加复杂度
   - 带来新问题
   - 不要为了分布而分布

2. 分阶段演进
   - 先解决当前问题
   - 为未来预留空间
   - 避免过度设计

3. 监控分布式系统
   - 监控节点健康
   - 监控网络延迟
   - 监控数据一致性

决策 4:同步 vs 异步

问题

什么时候用同步,什么时候用异步?

方案对比

方案延迟一致性复杂度适用场景
同步强一致关键操作
异步最终一致高并发操作

决策

同步操作:
- 点赞/取消点赞
- 用户查询
- 排行榜更新

异步操作:
- 数据库写入
- 数据统计
- 通知发送
- Feed 流更新

原因:
1. 提升用户体验(响应快)
2. 降低系统压力
3. 提高吞吐量

经验教训

1. 核心链路同步
   - 用户直接感知的操作
   - 不能异步化

2. 非核心链路异步
   - 统计、分析、通知
   - 可以异步化

3. 异步要可靠
   - 消息队列持久化
   - 重试机制
   - 死信队列

决策 5:缓存策略

问题

如何选择缓存策略?

方案对比

策略命中率一致性内存占用
Cache Aside
Write Through
Write Behind

决策

点赞计数场景:

读多写少:
- 查询频率 >> 写入频率
- 适合 Cache Aside

写入频繁:
- 点赞操作频繁
- 需要异步写入

决策:Cache Aside + Write Behind

1. 读:Cache Aside(先查缓存,未命中查数据库)
2. 写:Write Behind(先写缓存,异步写数据库)

经验教训

1. 缓存要有 TTL
   - 防止冷数据
   - 定期刷新
   - 合理设置过期时间

2. 缓存要监控
   - 监控命中率
   - 监控失效时间
   - 优化缓存策略

3. 缓存要有降级
   - 缓存故障时降级到数据库
   - 保证可用性

决策 6:分片 vs 不分片

问题

什么时候需要分片?

方案对比

方案复杂度性能扩展性
不分片
分片

决策

不分片:
- 单个 key 竞争不大
- Redis QPS < 5000

分片:
- 爆款文章
- 单 key 竞争大
- Redis QPS > 5000

分片数:
- 10 个分片(初期)
- 50 个分片(增长期)
- 100 个分片(成熟期)

经验教训

1. 先监控再分片
   - 确认是瓶颈
   - 分析热点 key
   - 避免过早分片

2. 分片数要适中
   - 太少:效果不明显
   - 太多:管理复杂
   - 建议:10-100 个

3. 支持动态分片
   - 根据负载调整
   - 自动扩缩容

决策总结

关键决策清单

✅ 已做对的决策:

1. 从简单开始
   - 初期用数据库
   - 增长后引入 Redis
   - 逐步优化

2. 选择最终一致
   - 性能优先
   - 对账保证一致
   - 用户可接受

3. 分阶段演进
   - 不追求一步到位
   - 根据问题优化
   - 保持架构灵活

4. 监控驱动
   - 数据说话
   - 问题导向
   - 及时调整

❌ 可以改进的决策:

1. 早期未考虑 Feed 流
   - 后期才补充
   - 应该提前规划

2. 分片数固定
   - 应该动态调整
   - 根据负载变化

3. 监控不够完善
   - 应该更细粒度
   - 及时发现问题

课后练习

练习 1

如果在项目初期就引入所有技术(Redis、分布式锁、分片等),会有什么问题?

参考答案(3 个标签)
系统设计过度设计架构

问题分析:

过度设计的问题:

1. 开发周期长
   - 需要实现各种复杂功能
   - 测试成本高
   - 延误上线时间

2. 维护成本高
   - 需要专业知识
   - 问题难以排查
   - 团队学习成本高

3. 资源浪费
   - 小用户量用分布式
   - 服务器资源闲置
   - 成本增加

4. 复杂度高
   - 出问题难定位
   - 修改影响面大
   - 团队协作困难

正确做法:

MVP 原则(最小可行产品):
1. 先用最简单的方案
2. 快速上线验证
3. 收集真实数据
4. 根据数据优化

优化时机:
- 确实是瓶颈
- 有数据支撑
- 成本收益合理

练习 2

如何评估”是否需要引入某个技术”?

参考答案(3 个标签)
技术选型决策方法架构

评估框架:

决策矩阵:

技术引入决策矩阵:

因素 | 权重 | 评分 (1-5) |
|------|------|-----------|
| 当前问题严重性 | 高 | ? |
| 技术成熟度 | 中 | ? |
| 团队能力 | 高 | ? |
| 时间紧迫度 | 中 | ? |
| 资源可用性 | 中 | ? |

总分 > 15:建议引入
总分 < 10:暂不引入

练习 3

如何处理”技术债务”?

参考答案(3 个标签)
技术债务重构架构演进

技术债务管理:

什么是技术债务?

技术债务:
- 为了快速上线,选择了简单但不完美的方案
- 欠下的技术债需要偿还

债务分类:
1. 代码质量债务
   - 代码混乱
   - 缺乏测试
   - 文档缺失

2. 架构债务
   - 设计不合理
   - 扩展性差
   - 可维护性差

3. 性能债务
   - 有性能瓶颈
   - 未优化查询
   - 资源浪费

4. 安全债务
   - 有安全隐患
   - 未做鉴权
   - 数据未加密

偿还策略:

重构时机:

1. 小步重构
   - 每次重构小范围
   - 频繁重构
   - 持续改进

2. 重构红线
   - 不影响功能
   - 保持测试通过
   - 代码审查

3. 重构优先级
   - 高影响高成本低:优先
   - 高影响高成本:评估
   - 低影响低成本低:暂缓

练习 4

如何在”功能完整性”和”开发速度”之间平衡?

参考答案(3 个标签)
MVP敏捷开发产品管理

平衡策略:

MVP(最小可行产品)原则:

第一版(MVP):
核心功能:
- ✅ 点赞功能
- ✅ 点赞计数
- ✅ 基本查询

暂缓功能:
- ❌ 排行榜
- ❌ Feed 流
- ❌ 高级统计

第二版(增长):
- ✅ 排行榜
- ✅ Feed 流
- ❌ 实时通知

第三版(优化):
- ✅ 实时通知
- ✌ 高级统计
- ✅ 数据分析

决策标准:

功能优先级评估:

P0(必须有):
- 核心业务功能
- 用户最需要的
- 竞争对手都有

P1(应该有):
- 提升体验的功能
- 用户期望的功能
- 实现成本不高

P2(可以有):
- 锦上添花的功能
- 实现成本高的功能
- ROI 不明确

练习 5

如何在”性能”和”可维护性”之间平衡?

参考答案(3 个标签)
代码质量性能优化可维护性

平衡策略:

性能 vs 可维护性:

场景 1:简单但慢
代码:简单易懂
性能:差
→ 优化:增加缓存

场景 2:复杂但快
代码:复杂难懂
性能:好
→ 优化:添加注释,提取函数

场景 3:简单且快
代码:简单易懂
性能:好
→ 保持:这是理想状态

场景 4:复杂且慢
代码:复杂难懂
性能:差
→ 重构:简化并优化

代码质量标准:

思考题

  1. 如果团队技术能力有限,应该如何调整架构设计?

  2. 如何在”快速上线”和”架构优雅”之间找到平衡?

  3. 技术选型时,应该参考哪些信息?

💡 提示:这些问题没有标准答案,建议结合实际情况深入思考。