DDIA Part 1: 数据系统基础

本文是 DDIA 第一部分的完整读书笔记,涵盖第 1-4 章:可靠性与可扩展性、数据模型、存储引擎、数据编码。


第1章:可靠性、可扩展性与可维护性

1.1 数据密集型应用的组成

现代数据密集型应用通常由多个组件组合:

1
2
3
4
5
6
7
8
9
10
┌─────────────────────────────────────────────────────────────┐
│ 数据密集型应用架构 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 数据库 │ │ 缓存 │ │ 搜索索引 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 流处理 │ │ 批处理 │ │ 消息队列 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
类型 特点 主要瓶颈 典型场景
数据密集型 数据量大、复杂、变化快 磁盘I/O、网络带宽 Web应用、社交网络
计算密集型 计算量大、算法复杂 CPU处理能力 科学计算、图形渲染

1.2 可靠性(Reliability)

可靠性:系统在面对故障时,仍能正确运行

术语 英文 定义
故障 Fault 系统中某个组件偏离其规格
失效 Failure 整个系统停止为用户提供服务
容错 Fault-tolerant 系统能够处理某些类型的故障

故障类型及应对

1. 硬件故障

  • 硬盘损坏、内存故障、电源中断
  • 应对:RAID、双电源、热备份、多副本

2. 软件错误

  • 比硬件故障更难预测,可能导致系统性问题
  • 应对:全面测试、进程隔离、监控告警

3. 人为错误

  • 研究表明,运维人员的配置错误是系统中断的首要原因
  • 应对:良好的API设计、沙箱环境、快速回滚

1.3 可扩展性(Scalability)

可扩展性:系统应对负载增长的能力

描述性能:百分位数

百分位 含义 用途
p50 中位数 典型响应时间
p95 95%的请求快于此值 较高标准
p99 99%的请求快于此值 SLA标准

Twitter 案例:推拉模式

方案 描述 优点 缺点
拉模式 读取时间线时查询关注者的推文 写入简单 读取开销大
推模式 发推时写入所有关注者的缓存 读取快速 写入开销大(大V问题)
混合模式 普通用户推模式,大V拉模式 平衡读写 实现复杂

扩展策略

  • 纵向扩展:使用更强大的机器(简单但有上限)
  • 横向扩展:使用多台普通机器(需要复杂设计)
  • 弹性扩展:根据负载自动增减资源

1.4 可维护性(Maintainability)

软件的大部分成本不在于最初的开发,而在于后续的维护

方面 目标
可操作性 让运维团队能够轻松保持系统运行
简单性 让新工程师能够轻松理解系统
可演化性 让工程师能够轻松对系统进行修改

第2章:数据模型与查询语言

2.1 数据模型分层

1
2
3
4
5
6
7
8
9
┌─────────────────────────────────────────────┐
│ 应用程序层(对象、数据结构、API) │
├─────────────────────────────────────────────┤
│ 数据模型层(表、文档、图) │
├─────────────────────────────────────────────┤
│ 存储层(字节序列、内存、磁盘) │
├─────────────────────────────────────────────┤
│ 硬件层(电信号、磁场) │
└─────────────────────────────────────────────┘

2.2 关系模型

概念 描述 示例
关系(Relation) users表
元组(Tuple) 行/记录 一个用户记录
属性(Attribute) name, email, age
1
2
3
4
5
6
7
8
9
10
11
-- 规范化设计:使用外键引用
CREATE TABLE positions (
position_id INT PRIMARY KEY,
title VARCHAR(100)
);

CREATE TABLE users (
user_id INT PRIMARY KEY,
name VARCHAR(100),
position_id INT REFERENCES positions(position_id)
);

优势:数据一致性、灵活查询、ACID事务、成熟稳定
局限:对象-关系阻抗不匹配、模式僵化、扩展困难

2.3 文档模型

代表产品:MongoDB, CouchDB, Elasticsearch

1
2
3
4
5
6
7
8
9
{
"user_id": 1,
"name": "张三",
"positions": [
{"title": "软件工程师", "company": "ABC公司"},
{"title": "技术总监", "company": "XYZ公司"}
],
"skills": ["Java", "Python", "分布式系统"]
}
特性 文档模型 关系模型
数据结构 嵌套/层次化 扁平/规范化
模式 灵活(Schema-on-read) 严格(Schema-on-write)
局部性 好(整个文档一起存储) 差(需要JOIN多表)
多对多关系 较难处理 容易处理

2.4 图模型

适用场景:社交网络、知识图谱、推荐系统

1
2
3
4
5
6
7
8
// Neo4j Cypher 示例
CREATE (alice:Person {name: 'Alice', age: 30})
CREATE (bob:Person {name: 'Bob', age: 25})
CREATE (alice)-[:FOLLOWS {since: '2020-01-01'}]->(bob)

-- 找出 Alice 关注的人所关注的人
MATCH (alice:Person {name: 'Alice'})-[:FOLLOWS]->()-[:FOLLOWS]->(fof)
RETURN fof.name

2.5 数据模型选择指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
     ┌───────────────┐
│ 数据关系复杂吗?│
└───────────────┘
│ │
复杂 简单
│ │
▼ ▼
┌──────────┐ ┌─────────────┐
│多对多关系?│ │树状/层次结构?│
└──────────┘ └─────────────┘
│ │ │ │
是 否 是 否
▼ ▼ ▼ ▼
图模型 关系模型 文档模型 关系模型

第3章:存储与检索

3.1 两大存储引擎家族

类型 优化目标 典型应用 代表产品
日志结构存储 写入优化 高写入负载 LevelDB, RocksDB, Cassandra
原地更新存储 读取优化 事务处理 MySQL InnoDB, PostgreSQL

3.2 哈希索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────────────────┐
│ 内存中的哈希表 │
├─────────────────────────────────────────────┤
│ key1 ──> 字节偏移量 0 │
│ key2 ──> 字节偏移量 128 │
│ key3 ──> 字节偏移量 256 │
└─────────────────────────────────────────────┘


┌─────────────────────────────────────────────┐
│ 磁盘上的日志文件 │
├─────────────────────────────────────────────┤
│ offset 0 : key1,value1 │
│ offset 128: key2,value2 │
└─────────────────────────────────────────────┘

局限:所有键必须在内存中、范围查询慢

3.3 LSM-Tree(Log-Structured Merge-Tree)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Level 0 (内存):
┌────────────┐
│ Memtable │ ← 当前写入(平衡树)
└────────────┘

Level 1 (磁盘):
┌────┐ ┌────┐ ┌────┐
│SS1 │ │SS2 │ │SS3 │ ← 较新的 SSTable
└────┘ └────┘ └────┘

Level 2 (磁盘):
┌────────┐ ┌────────┐
│ SS4 │ │ SS5 │ ← 经过合并的 SSTable
└────────┘ └────────┘

优化技术

  • 布隆过滤器:快速判断键是否存在(避免无效磁盘读取)
  • 压缩策略:Size-Tiered(写密集)、Leveled(读密集)

3.4 B-Tree

1
2
3
4
5
6
7
8
9
             ┌───────────────┐
│ [30, 70] │ ← 根节点
└───────────────┘
/ │ \
┌───────┐ ┌───────────┐ ┌───────┐
│[10,20]│ │[40,50,60] │ │[80,90]│ ← 内部节点
└───────┘ └───────────┘ └───────┘
/ │ \ / │ \ \ / │ \
叶子节点(包含实际数据或指向数据的指针)
特性 B-Tree LSM-Tree
写入 原地更新 追加写入
读取 快(一次定位) 可能需要检查多个文件
写放大 较低 较高(压缩开销)
空间利用 可能碎片化 更紧凑

3.5 OLTP vs OLAP

特性 OLTP OLAP
全称 在线事务处理 在线分析处理
主要操作 增删改查 复杂查询、聚合
访问模式 基于键的随机访问 扫描大量记录
数据量 GB~TB TB~PB
响应时间 毫秒级 秒~分钟级

列式存储优势

  • 只读取需要的列
  • 相同类型数据更易压缩
  • 向量化处理

第4章:数据编码与演化

4.1 兼容性概念

1
2
3
4
5
6
7
后向兼容(Backward Compatibility):
新代码能读取旧代码写的数据
v3 代码 ─── 读取 ───> v1 数据 ✓

前向兼容(Forward Compatibility):
旧代码能读取新代码写的数据
v1 代码 ─── 读取 ───> v3 数据 ✓

滚动升级:不停机部署新版本,同时运行新旧版本代码

4.2 编码格式对比

特性 JSON Protobuf Thrift Avro
可读性
空间效率 最高
模式 可选 必须 必须 必须
模式演化 手动 支持 支持 支持
动态模式 天然 困难 困难 支持

4.3 Protocol Buffers 示例

1
2
3
4
5
message Person {
required string user_name = 1;
optional int64 favorite_number = 2;
repeated string interests = 3;
}

兼容性规则

操作 后向兼容 前向兼容
添加可选字段
删除可选字段
添加必填字段
修改字段标签

4.4 数据流模式

1. 数据库:数据可能比代码更持久,需要能够读取多年前写入的数据

2. 服务调用(REST/RPC)

  • REST:基于HTTP,资源导向
  • gRPC:基于Protobuf,高性能

3. 消息传递

  • 解耦、缓冲、异步、可靠性
  • 代表:Kafka, RabbitMQ

本章关键要点

  1. 可靠性不是追求零故障,而是在故障发生时系统仍能正常工作
  2. 百分位数比平均值更能反映用户真实体验
  3. 抽象是管理复杂性的最重要工具
  4. 没有万能的数据模型,选择取决于应用场景
  5. LSM-Tree优化写入,B-Tree优化读取
  6. 兼容性是渐进式部署的前提

延伸阅读

  • 《Site Reliability Engineering》:Google SRE 实践
  • 《Database Internals》:数据库内部原理深入指南
  • 《Building Microservices》:服务间通信详解