极客时间:后端存储实战课
以电商不同发展阶段的场景为蓝本,介绍不同阶段遇到的问题及相关解决办法。
- 创业篇:用尽可能简单的架构,
正确地保存业务事实 - 高速增长篇:保护数据库
- 海量数据篇:分布和异构
即如何设计一个数据系统提到的:在不断变化的 Workload 下,如何演进数据模型、读写路径和分布式策略。
核心观点
- 存储设计的起点不是技术选型,而是业务
读写模式:读多还是写多、是否强一致、数据量增长速度、查询是点查/范围查/全文检索还是聚合分析 - 交易系统的第一目标始终是
正确性。订单、账户、库存、支付这些数据一旦出错,后果通常不是页面慢一点,而是资金损失、对账困难和业务状态混乱。 - 高并发系统的第一优化目标也不是
更快,而是先保护 MySQL主库。缓存、读写分离、搜索引擎、异步化,本质上都在帮助主库从高频流量中脱身。 - 不同类型的数据需要不同类型的存储:MySQL 负责交易主数据,Redis 负责热点缓存,Elasticsearch 负责全文搜索,对象存储负责文件,分析型或分布式存储负责点击流和日志。
分库分表不是默认答案,而是最后一招。在那之前,应该先做 SQL 和索引优化、缓存、读写分离、归档和冷热分离。
一页速记
- 先看业务,再选存储
- 交易系统核心是数据不能错
- 创建幂等靠唯一
ID + 唯一约束 - 更新幂等靠
version乐观锁 - 单库一致性靠事务
- 跨系统一致性优先考虑最终一致
- 高并发先缓存,先保护
MySQL - 搜索交给
Elasticsearch - 数据增长先归档、冷热分离,再考虑分库分表
- 高可用靠备份、
Binlog、复制、切换、恢复 - 图片文件用对象存储
- 日志、点击流别继续塞交易库
- 分库分表是最后一招
实战要点
1. 交易系统先保正确
课程最重要的一条线索,是把订单、账户、库存这类系统统一看成交易数据系统。这类系统最关键的问题从来不是吞吐,而是数据不能错。
创建订单时,不能只靠前端防重,因为用户重复点击、网络超时、RPC 重试、网关重试都会制造重复请求。更稳妥的做法是:
- 预先生成全局唯一订单号
- 用订单号作为主键
- 利用唯一约束兜底实现创建幂等
这相当于把重复请求转成重复写同一个主键。
更新订单时,重点不是重复创建,而是并发覆盖和 ABA 问题。课程给出的典型做法是给订单表加 version 字段,用乐观锁保证:
- 更新时携带旧版本号
- SQL 里比较版本号
- 更新成功后
version + 1
本质上,这是让更新操作具备我改的是我看到的那个版本的约束。
这一套思路不仅适用于订单系统,也能迁移到很多需要幂等和并发保护的业务里。它和 一致性、高并发系统实战课 里的很多问题是同一类。
2. 账户系统必须同时保存结果和过程
账户系统这部分的重点不只是事务,而是一个很实用的设计习惯:
- 余额表保存当前结果
- 流水表保存完整过程
如果只有余额没有流水,系统虽然能查到现在还剩多少钱,但无法解释为什么变成这样,也没法做审计、回放和对账。
课程对事务的使用也很务实。事务解决的是单库内多次写操作的一致性,例如:
- 更新余额
- 写入账户流水
这两步必须在同一个事务里完成,否则就会出现余额改了但没流水或者有流水但余额没改的错误状态。
这里最值得记住的不是 ACID 的定义,而是:
- 事务是管理复杂性的手段
- ACID 是目标,不是免费能力
- 实际系统总在一致性和性能之间做隔离级别上的取舍
3. 跨系统一致性优先考虑最终一致
课程在分布式事务这块给出的判断很清晰:
- 强一致需求特别高时,可以考虑
2PC - 更常见、更实用的是本地消息表和最终一致性
这背后的原因不是最终一致更先进,而是强一致通常更贵、更慢、更脆弱。系统一旦拆成多个服务、多个数据库,单库事务就失效了。真正的工程做法是:
- 先把大事务拆成多个本地事务
- 再设计消息、重试、补偿和幂等
也就是说,跨系统一致性的关键不只是某个事务协议,而是整条链路是否支持:
- 幂等
- 可重试
- 可补偿
- 可观测
这部分和 设计数据密集型应用、分布式系统 放在一起看会更清楚。
4. 高并发的第一目标是保护主库
商品详情页、首页、搜索结果页这类场景,最典型的特点是:
- 读多写少
- 热点集中
- 请求量远高于交易链路
课程对这类问题的思路不是一味扩数据库,而是先做存储分层:
- 热点数据走缓存
- 搜索类查询交给 ES
- 交易主数据仍然以 MySQL 为准
这里有个很重要的现实判断:
缓存的第一目标不是“更快”,而是“让数据库别被打死”。
Redis 在课程里的定位就很明确,它是高并发下的热点承压层,不是唯一事实来源。也正因为如此,使用缓存时真正麻烦的问题不是“怎么读得更快”,而是:
- 缓存穿透
- 缓存击穿
- 缓存雪崩
- 缓存和数据库的一致性
课程后面提到 MySQL 到 Redis 同步时,也在强调同一个方向:不要把所有缓存刷新逻辑都塞进业务代码,应该尽量借助 Binlog、消息队列或异步链路,把缓存更新做成一条更稳定的数据同步机制。
5. 搜索、检索、分析不要硬塞给 MySQL
课程用 Elasticsearch 讲了一个很常见的误区:很多系统不是不知道 ES 好用,而是总想让 MySQL 继续兼顾全文检索。
问题在于两者擅长的事根本不同:
- MySQL 擅长事务、约束、点查、范围查
- ES 擅长倒排索引和全文搜索
所以搜索系统更像是搜索视图,而不是交易主库。它解决的是:
- 分词
- 召回
- 相关性排序
- 高性能检索
同样的思路也适用于点击流、日志、埋点、监控这类海量追加写数据。课程后面在讲海量数据、NewSQL、RocksDB 时,本质上都在说明:
不同访问模式应该由不同存储系统分工承担,而不是让一套数据库兼顾所有事情。
6. 分库分表是最后一招
这门课非常值得保留的一点,是它没有把分库分表包装成一种高级架构标配,而是明确指出它的代价:
- 跨分片查询复杂
- 跨分片事务困难
- 主键设计复杂
- 扩容复杂
- 数据迁移复杂
- 运维复杂
因此处理数据量增长时,更推荐的顺序是:
- 先优化 SQL、索引、表结构
- 再用缓存承接读流量
- 再做读写分离
- 再做归档和冷热分离
- 最后才考虑分库分表
这个顺序很实用,因为很多系统其实在前 3 到 4 步就已经能把问题解决掉,没必要过早引入分片复杂度。它和 CS++分库分表、CS++数据分区 可以互相参照。
评论