CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
软件架构师入门/课时/数据架构
RawBlame
课时目录12 节

软件架构师入门

  1. 01软件架构是什么
  2. 02架构师软技能
  3. 03设计模式与原则
  4. 04架构风格
  5. 05数据架构
  6. 06API 与集成
  7. 07安全架构
  8. 08云与设计模式
  9. 09网络基础
  10. 10技术栈选型
  11. 11治理与方法论
  12. 12综合练习

数据架构

CAP、NoSQL 与数仓。

30 分钟

数据架构

这一课你将能做到

  1. 用 CAP 定理解释「为什么网络分区时必须在一致性和可用性之间二选一」
  2. 为本站设计缓存方案:选型 Redis、确定缓存模式、规避缓存穿透/击穿/雪崩
  3. 判断一个业务场景该用 MySQL、Redis、MongoDB 还是 Elasticsearch,并给出理由

为什么要学

数据是系统里最「重」的东西——代码可以重构,服务可以重写,但几 TB 的数据迁移一次伤筋动骨。数据架构选错,后期纠正的代价是架构中最高的。

人话:选数据库就像选仓库。MySQL 是带账本的管理严格的仓库(入库慢但账目清晰),Redis 是店门口的快取货架(拿得快但地方小、可能丢),MongoDB 是什么都往里扔的大平层(灵活但找东西费劲)。

先建立图像

本站数据架构演进:

【阶段一】                    【阶段二】               【阶段三】
App ──→ MySQL(单库)     App ──→ Redis ──→ MySQL     订单服务 ──→ MySQL(订单库)
                          (缓存)    (主→从)         课程服务 ──→ MySQL(课程库)
                                                    搜索服务 ──→ Elasticsearch
                                                    行为埋点 ──→ Kafka ──→ ClickHouse

CAP 定理图解:

        C(一致性)
       / \
      /   \
     /     \
    A ────── P
 可用性    分区容错性

网络分区必然发生(P 必须选),所以只能选 CP 或 AP:
- CP:ZooKeeper、HBase、etcd —— 宁可不可用,不能不一致
- AP:Cassandra、DynamoDB、Redis 主从 —— 宁可旧数据,不能不可用

跟着做

步骤 1:用 ACID 分析订单场景

本站订单表设计:

CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    course_id BIGINT NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
    version INT NOT NULL DEFAULT 0,  -- 乐观锁
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_user_status (user_id, status)
) ENGINE=InnoDB;

ACID 逐条落地:

  • 原子性:下单 = 插订单 + 扣库存,用本地事务包住
  • 一致性:status 只能是枚举值,用 CHECK 或应用层校验
  • 隔离性:默认 REPEATABLE READ,热点行用乐观锁 version 字段
  • 持久性:innodb_flush_log_at_trx_commit=1, redo log 落盘

步骤 2:缓存三大经典问题实战

本站课程详情接口(QPS 500)加 Redis 缓存:

// 缓存穿透:查不存在的课程 id,请求打到 DB
public Course getCourse(long id) {
    String key = "course:" + id;
    Course c = redis.get(key);
    if (c != null) return c;
    c = db.findById(id);
    // 关键:空值也缓存 60 秒,防穿透
    redis.set(key, c == null ? NULL_PLACEHOLDER : c, 
              c == null ? 60 : 3600);
    return c;
}
问题
现象
本站解法
穿透恶意刷不存在的 id空值缓存 + 布隆过滤器
击穿热点 key 过期瞬间大量请求打 DB互斥锁(SETNX)单线程回源
雪崩大量 key 同时过期过期时间加随机抖动(3600 ± 300 秒)

步骤 3:缓存模式选型

Cache-Aside(本站采用):
读:先读缓存 →  miss → 读 DB → 写缓存
写:先写 DB → 删缓存(不是更新!)

Read/Write-Through: 缓存代理 DB,应用只跟缓存说话
Write-Behind: 先写缓存,异步批量刷 DB(快但可能丢)

面试必答点:为什么写操作是删缓存而不是更新缓存? 因为更新可能并发错乱(A 写 DB → B 写 DB → B 更新缓存 → A 更新缓存,缓存里反而是旧值),删除配合延迟双删更安全。

讲清楚要点

  1. CAP 的真实含义:分区容错 P 在分布式系统里必须保证,真正的选择是分区发生时选 C 还是选 A。正常无分区时两者可兼得。
  2. BASE 是对 ACID 的工程妥协:基本可用(Basically Available)、软状态(Soft state)、最终一致(Eventually consistent)。本站的课程浏览量就是 BASE 场景——丢几百次计数无所谓。
  3. NoSQL 选型看数据模型:KV 选 Redis、文档选 MongoDB、列式选 ClickHouse/HBase、图选 Neo4j、搜索选 ES。不要用 MongoDB 模拟关系表。
  4. 索引是 MySQL 性能的第一杠杆:B+ 树索引、覆盖索引、最左前缀。本站 idx_user_status 支撑「我的订单列表」查询,避免全表扫描。
  5. 分库分表是最后手段:能用索引解决就别分表,能用分表解决就别分库。分库分表引入分布式事务、跨片 join、全局 ID 一系列问题。

你可能会懵的地方

  • Redis 这么快,能不能直接当主库? 不能。Redis 的持久化(RDB 快照/AOF 日志)可靠性远低于 InnoDB,内存成本高,且缺乏复杂查询能力。它定位是缓存和轻量存储(Session、计数器、排行榜)。

  • 最终一致性,业务怎么接受? 用「读己之写」兜底:用户刚改完的资料强制读主库或写缓存,其他用户看到旧数据无所谓。把一致性要求细化到场景,而不是整个系统一刀切。

  • MySQL 和 PostgreSQL 怎么选? 两者都足够好。MySQL 生态大、中间件多、国内招聘容易;PG 功能强(JSONB、GIS、CTE)。团队熟悉度优先,本站选 MySQL 因为 DBA 资源和云上 RDS 成熟。

  • 数据仓库和数据库的区别? 数据库(OLTP)面向事务:小批量、高频、实时;数仓(OLAP)面向分析:大批量、低频、离线。本站订单在 MySQL(OLTP),用户行为日志进 ClickHouse(OLAP)做漏斗分析。

  • 要不要上 Hadoop/Spark? 看数据量。日增量低于 1TB 没必要上 Hadoop,用 Kafka + Flink 或直接用 ClickHouse 就够。大数据平台运维成本极高,小团队会被拖垮。

错法对照

错法问题正解
缓存和 DB 双写更新并发下缓存是脏数据Cache-Aside:写 DB 后删缓存
所有 key 过期时间设成一样同时过期引发雪崩基础时间 + 随机抖动
一张表 50 个字段还继续加行太长,查询和索引效率都差垂直拆分:主表 + 扩展表
用 MySQL 存用户行为日志写入洪峰拖垮主库Kafka 削峰 → ClickHouse
为了「以后分库」提前用分布式 ID增加复杂度却用不上初期自增 ID 够用,分库时再迁移

卡点急救

  1. 慢查询拖垮数据库:先 SHOW PROCESSLIST 找元凶,再 EXPLAIN 看执行计划,90% 的慢查询是缺索引或索引失效(函数操作、隐式转换、前导通配符)。
  2. Redis 内存爆了:INFO memory 看占用,检查是否有大 key(--bigkeys),设置 maxmemory-policy 为 allkeys-lru,给所有 key 加 TTL。
  3. 主从延迟导致读到旧数据:关键读(支付后查订单)强制走主库,非关键读走从库;或监控延迟超过阈值时自动切主。

面试常问

  • CAP 定理怎么理解?能同时满足吗? 网络分区(P)在分布式系统中必然发生,因此只能在一致性(C)和可用性(A)之间权衡:CP 系统在分区时拒绝服务保证一致(如 ZooKeeper,选主期间不可用);AP 系统分区时继续服务但可能返回旧数据(如 Redis 主从切换间隙)。无分区时 CA 可兼得。举例本站:订单服务选 CP(钱不能错),课程浏览量选 AP(数字晚点更新无所谓)。

  • 缓存穿透、击穿、雪崩的区别和解决方案? 穿透:查不存在的数据,缓存和 DB 都没有——解法:空值缓存 + 布隆过滤器。 击穿:热点 key 恰好过期,并发回源打爆 DB——解法:互斥锁单线程回源,或热点数据不过期。 雪崩:大量 key 同时过期或 Redis 宕机——解法:过期时间加随机值、多级缓存、熔断降级。 加分项:说清楚三者的根因都是「缓存失效后请求直达 DB」。

  • MySQL 索引为什么用 B+ 树? 对比答题:哈希索引不支持范围查询;二叉树太高导致 IO 次数多;B 树非叶子节点存数据,每页能放的 key 少;B+ 树非叶子只存 key,3 层就能存千万级数据,且叶子节点链表天然支持范围扫描。结合 InnoDB 聚簇索引讲:主键索引叶子存整行数据,二级索引叶子存主键值(回表)。

自测三问

  1. 本站的「课程搜索」功能数据量到 50 万后,该从 MySQL 迁移到什么?同步方案怎么做?
  2. 写操作为什么推荐「删缓存」而不是「更新缓存」?并发场景下还有什么坑?
  3. 如果本站要做「用户学习漏斗分析」(浏览→试听→购买),原始数据应该存哪里?

小结一句话

数据架构的核心是按「一致性要求、读写比例、数据量级」三要素匹配存储引擎,并用缓存模式保护最脆弱的数据库层。

延伸

  • 《数据密集型应用系统设计》(DDIA, Martin Kleppmann)——数据架构圣经
  • MySQL 官方文档:InnoDB 索引章节
  • Redis 官方文档:缓存模式(cache patterns)
  • 下一课:API 与集成——REST、GraphQL、gRPC 与消息队列的选型。
加载本节练习…
← 上一课架构风格下一课 →API 与集成