Google Bigtable:分布式结构化存储的开山之作,HBase 就是照着它做的
关系型数据库在数据量到 PB 级的时候撑不住了——单机存不下、分库分表太复杂、高并发读写扛不住。
Google 的 Bigtable 就是在这个背景下诞生的:一个专门为”海量结构化数据”设计的分布式存储系统。
它影响了后来几乎所有 NoSQL 数据库的设计思路。HBase 就是 Bigtable 的开源实现——数据模型一样、架构一样、连组件名字都是对应的。
Bigtable 的数据模型:一张三维的稀疏大表
Bigtable 的数据模型可以用一句话概括:
一个稀疏的、分布式的、多版本的三维映射表。
1 | (row, column, timestamp) → value |
拆开看四个概念:
1. 行(Row)
每行有一个唯一的 Row Key,按字典序排序。
排序这个设计很关键: 相近的 Row Key 会存到一起,范围查询效率极高。比如你要查 user_001 到 user_100 的数据,它们物理上连续,一次扫描就能拿到。
2. 列(Column)
列分两级:列族(Column Family)+ 限定符(Qualifier)。
格式是 family:qualifier,比如 user:name、user:age、log:timestamp。
- 列族 必须预先定义,是存储和权限控制的基本单位
- 限定符 不用预先定义,随时可以加
这个设计让 Bigtable 非常灵活: 你可以在同一个列族下动态添加新列,不用改表结构。
3. 时间戳(Timestamp)
每个单元格可以存多个版本,用时间戳区分:
1 | Row Key: user_001 |
可以配置保留最近 N 个版本,或者保留某个时间范围内的版本。
4. 单元格(Cell)
(Row Key, Column, Timestamp) 三者唯一确定一个值。值就是普通的字符串(二进制安全,可以存任意数据)。
Bigtable 的数据模型 vs 关系型数据库
| 关系型数据库 | Bigtable | |
|---|---|---|
| 表结构 | 预定义 schema,列固定 | 列族预定义,限定符动态添加 |
| 数据行 | 有主键,但无排序保证 | 按 Row Key 字典序排序 |
| 多版本 | 不支持(需自己实现) | 原生支持多版本时间戳 |
| 稀疏性 | 每行都有所有列 | 空的单元格不占存储 |
Bigtable 适合”半结构化”数据——每行字段不完全一样,比如用户行为日志、爬虫抓取的数据。
系统架构:Master 管调度,Tablet Server 存数据
Bigtable 是典型的”管理 + 执行”分离架构:
graph TD
A[客户端 Client] -->|RPC| B[主控服务器 Master]
A -->|直接 RPC| C[子表服务器 Tablet Server1] & D[Tablet Server2]
B -->|管理/监控| C & D
C -->|存储数据| E[GFS
SSTable + 操作日志]
D -->|存储数据| E
B -->|依赖| F[Chubby
分布式锁/选举]
C -->|依赖| F
note[核心依赖 GFS 提供持久化,Chubby 提供分布式协调]
三个核心组件:
| 组件 | 管什么 | 类比 |
|---|---|---|
| Master | 子表分配、负载均衡、故障恢复、元数据管理 | 指挥官,不下场干活 |
| Tablet Server | 实际存数据、处理读写请求 | 一线执行者 |
| Client | 应用程序接口,缓存子表位置 | 用户的门面 |
底层依赖两个基础设施:
- GFS:存数据文件的”硬盘”(SSTable + 操作日志)
- Chubby:分布式锁服务,选 Master、存根表位置
子表(Tablet):数据分片的基本单位
Bigtable 把一张大表按 Row Key 范围切成多个连续的子表(Tablet)。
- 初始时一张表只有一个 Tablet
- 数据量超过阈值(100-200MB)自动分裂成两个
- Tablet 可以在 Tablet Server 之间迁移,实现负载均衡
定位一个 Tablet 的流程(三级元数据):
1 | Client → 根表(Root Tablet)→ 元数据表 → 目标 Tablet → Tablet Server |
- 根表:只有一张,存元数据表的位置,永不分裂,存储在 Chubby 里
- 元数据表:存所有用户表的 Tablet 位置信息
- 用户表:实际业务数据
HBase 的 .META. 表就是照搬这个设计的。
读写流程:内存 + 日志 + SSTable
写操作:
- Client 定位到目标 Tablet Server
- 写操作先记到 操作日志(WAL)(GFS 上,确保不丢)
- 更新内存中的 MemTable(有序结构)
- MemTable 满了之后,异步刷到 GFS 生成 SSTable 文件
读操作:
- Client 定位到目标 Tablet Server
- 合并读取 MemTable(内存中最新数据)+ SSTable(磁盘上已持久化的数据)
- 按时间戳返回最新版本
为什么写快? 写只涉及内存 + 追加日志,不涉及随机磁盘 IO。为什么读不慢? SSTable 是按 Row Key 排序的,查询是二分查找。
Bigtable 的核心设计思想
| 设计决策 | 为什么这么设计 |
|---|---|
| Row Key 排序 | 范围查询高效,数据局部性好 |
| 列族 + 动态限定符 | 灵活的数据模型,不用预先定义所有列 |
| 多版本时间戳 | 支持历史数据追溯,不用自己维护版本 |
| 子表自动分裂 | 数据量增长时自动扩展,不用人工干预 |
| 操作日志 + MemTable | 写入快 + 数据不丢 |
| Master 不参与读写 | Master 压力小,集群规模可以很大 |
Bigtable 与 HBase 的对照
HBase 就是 Bigtable 的开源实现,几乎照搬了设计:
| Bigtable | HBase |
|---|---|
| GFS | HDFS |
| Chubby | ZooKeeper |
| Master | HMaster |
| Tablet Server | RegionServer |
| Tablet | Region |
| SSTable | HFile |
| MemTable | MemStore |
| 操作日志(WAL) | WAL(Write-Ahead Log) |
理解了 Bigtable,就理解了 HBase。模型一样、架构一样、读写流程一样,只是名字换了。