mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2123 字
6 分钟
云原生存储
2025-07-05

“计算和存储在同一台机器上”——数据库 40 年来都基于这个假设设计。扩容必须一起扩,缩容必须一起缩:存储满了但 CPU 还闲着,或者 CPU 打满但磁盘还空着一半。云时代这个假设正在瓦解。Aurora 把 redo log 直接写到分布式存储层,数据库实例不再刷脏页;Neon 把存储层做成可按时间回溯的 Page Server,计算节点无状态、随时增减。

计算存储分离不是”把数据库搬到云上”,而是重新设计数据库的存储架构——让存储和计算各自按需扩展。

一、为什么需要计算存储分离#

1.1 传统架构的困境#

传统数据库(MySQL/PostgreSQL)采用计算存储耦合架构:每个数据库实例同时拥有计算资源(CPU/内存)和存储资源(本地磁盘)。

graph TB subgraph "传统架构:计算存储耦合" NODE1["节点 1<br/>CPU + 内存 + 本地磁盘"] NODE2["节点 2<br/>CPU + 内存 + 本地磁盘"] NODE3["节点 3<br/>CPU + 内存 + 本地磁盘"] NODE1 -.->|"主从复制"| NODE2 NODE1 -.->|"主从复制"| NODE3 end subgraph "问题" P1["存储浪费:3 份数据副本"] P2["扩缩容慢:需要数据拷贝"] P3["故障恢复慢:需要重放日志"] P4["只读副本延迟:异步复制"] end style P1 fill:#ffcdd2,stroke:#c62828 style P2 fill:#ffcdd2,stroke:#c62828 style P3 fill:#ffcdd2,stroke:#c62828 style P4 fill:#ffcdd2,stroke:#c62828
问题传统架构根因
存储浪费3 节点存 3 份完整数据每个节点独立存储
扩缩容慢增加副本需全量拷贝数据随计算节点绑定
故障恢复慢需要从 Checkpoint 重放 WAL本地存储不可用时数据丢失
只读副本延迟异步复制,秒级延迟复制整个数据页
升级停机滚动升级期间服务不可用计算和存储耦合升级

1.2 云原生存储的核心思想#

计算存储分离的核心思想:将数据持久化交给共享存储层,计算节点变成无状态

graph TB subgraph "云原生架构:计算存储分离" COMPUTE1["计算节点 1<br/>CPU + 内存(读写)"] COMPUTE2["计算节点 2<br/>CPU + 内存(只读)"] COMPUTE3["计算节点 3<br/>CPU + 内存(只读)"] SHARED["共享存储层<br/>多 AZ 冗余<br/>日志即数据库"] COMPUTE1 --> SHARED COMPUTE2 --> SHARED COMPUTE3 --> SHARED end subgraph "优势" A1["存储只存一份(冗余在存储层)"] A2["计算秒级扩缩容"] A3["故障秒级恢复"] A4["只读副本零延迟"] end style A1 fill:#c8e6c9,stroke:#2e7d32 style A2 fill:#c8e6c9,stroke:#2e7d32 style A3 fill:#c8e6c9,stroke:#2e7d32 style A4 fill:#c8e6c9,stroke:#2e7d32
维度传统架构计算存储分离
数据副本每个计算节点一份共享存储层一份(存储层内部冗余)
扩容拷贝数据,分钟级启动计算节点,秒级
故障恢复重放日志,分钟级新节点连接存储,秒级
只读副本异步复制,有延迟共享读取,无延迟
升级滚动升级,可能停机计算节点独立升级

二、Aurora:日志即数据库#

2.1 架构总览#

Amazon Aurora 是 AWS 的云原生关系数据库,核心创新是 “日志即数据库”(Log is Database)——计算节点只写 Redo Log 到共享存储,存储节点负责将日志重放为数据页。

graph TB subgraph "Aurora 架构" WRITER["写入节点<br/>MySQL 兼容"] READER1["只读节点 1"] READER2["只读节点 2"] READER3["只读节点 3"] LOG["Redo Log<br/>只写日志!"] SG1["存储节点组 1<br/>AZ-a: 2 副本<br/>AZ-b: 2 副本<br/>AZ-c: 2 副本"] SG2["存储节点组 2<br/>同上 6 副本"] SG3["存储节点组 3<br/>同上 6 副本"] SG4["存储节点组 4<br/>同上 6 副本"] SG5["存储节点组 5<br/>同上 6 副本"] SG6["存储节点组 6<br/>同上 6 副本"] SG7["存储节点组 7<br/>同上 6 副本"] SG8["存储节点组 8<br/>同上 6 副本"] SG9["存储节点组 9<br/>同上 6 副本"] SG10["存储节点组 10<br/>同上 6 副本"] WRITER --> LOG LOG --> SG1 & SG2 & SG3 & SG4 & SG5 & SG6 & SG7 & SG8 & SG9 & SG10 READER1 & READER2 & READER3 --> SG1 & SG2 & SG3 & SG4 & SG5 & SG6 & SG7 & SG8 & SG9 & SG10 end style WRITER fill:#e3f2fd,stroke:#1565c0 style LOG fill:#fff9c4,stroke:#f9a825 style SG1 fill:#c8e6c9,stroke:#2e7d32

2.2 Quorum 复制模型#

Aurora 使用 Quorum 协议保证数据一致性:

参数含义
总副本数6跨 3 个 AZ,每个 AZ 2 副本
写入 Quorum (Vw)4至少 4 个副本确认写入
读取 Quorum (Vr)3至少从 3 个副本读取
Vw + Vr > V4 + 3 > 6保证读取总能看到最新写入
# Quorum 读写伪代码
class AuroraQuorum:
TOTAL_REPLICAS = 6
WRITE_QUORUM = 4
READ_QUORUM = 3
def write(self, log_record):
"""写入:至少 4 个副本确认"""
acks = 0
for replica in self.replicas:
if replica.append_log(log_record):
acks += 1
if acks >= self.WRITE_QUORUM:
return True # 写入成功
return False # 写入失败
def read(self, lsn):
"""读取:至少 3 个副本返回,取最高 LSN"""
responses = []
for replica in self.replicas:
resp = replica.read_lsn()
responses.append(resp)
if len(responses) >= self.READ_QUORUM:
# 取最高 LSN 的响应
return max(responses, key=lambda r: r.lsn)

2.3 日志即数据库的工作流程#

传统数据库写入流程:
1. 写 Redo Log(WAL)
2. 写数据页到磁盘
3. 写 Undo Log
总 I/O:3 次写操作
Aurora 写入流程:
1. 写 Redo Log 到存储层(6 副本,4 确认)
2. 完成!
总 I/O:1 次写操作(日志)
存储节点后台操作:
1. 接收 Redo Log
2. 将日志重放为数据页(后台异步)
3. 定期生成 Checkpoint
4. 垃圾回收旧版本
Note

Aurora 的关键洞察:数据库只需要写日志,数据页可以由存储节点异步生成。这把网络 I/O 从”写数据页”(大)减少到”写日志”(小),通常减少 10-100 倍的网络流量。

2.4 只读副本#

-- Aurora 只读副本
-- 创建只读副本(秒级完成)
-- AWS CLI
aws rds create-db-instance-read-replica \
--db-instance-identifier aurora-reader-1 \
--source-db-instance-identifier aurora-cluster
-- 只读副本特点:
-- 1. 共享同一存储卷,无需数据拷贝
-- 2. 延迟通常 < 20ms
-- 3. 最多 15 个只读副本
-- 4. 自动负载均衡(Reader Endpoint)
-- 连接写入节点
mysql -h aurora-cluster.cluster-xxxxx.rds.amazonaws.com
-- 连接只读副本(自动路由)
mysql -h aurora-cluster.cluster-ro-xxxxx.rds.amazonaws.com
特性传统只读副本Aurora 只读副本
创建时间小时级(全量拷贝)分钟级(共享存储)
复制延迟秒级毫秒级
存储开销每副本一份完整数据共享存储,零额外开销
最大副本数通常 5 个15 个

三、Neon:Serverless PostgreSQL#

3.1 架构总览#

Neon 是一个开源的 Serverless PostgreSQL 实现,采用计算存储分离架构,核心创新是 Page Server + WAL Service + Safekeeper 的三层架构。

graph TB subgraph "Neon 架构" COMPUTE["计算节点<br/>PostgreSQL<br/>无状态"] PS["Page Server<br/>页面服务<br/>按需物化数据页"] SK1["Safekeeper 1<br/>WAL 持久化<br/>AZ-a"] SK2["Safekeeper 2<br/>WAL 持久化<br/>AZ-b"] SK3["Safekeeper 3<br/>WAL 持久化<br/>AZ-c"] S3["S3 对象存储<br/>归档 WAL + Checkpoint"] COMPUTE -->|"写 WAL"| SK1 & SK2 & SK3 COMPUTE -->|"读页面"| PS SK1 & SK2 & SK3 -->|"推送 WAL"| PS SK1 & SK2 & SK3 -->|"归档"| S3 PS -->|"从 S3 恢复"| S3 end style COMPUTE fill:#e3f2fd,stroke:#1565c0 style PS fill:#c8e6c9,stroke:#2e7d32 style SK1 fill:#fff9c4,stroke:#f9a825

3.2 核心组件#

组件作用类比
Compute Node无状态 PostgreSQL,处理 SQL 查询Aurora 的计算节点
Safekeeper持久化 WAL,保证不丢失Aurora 的存储节点(日志部分)
Page Server物化数据页,响应计算节点的页面请求Aurora 的存储节点(数据页部分)
S3归档 WAL 和 CheckpointAurora 的 S3 后端

3.3 分支:Neon 的杀手级特性#

# Neon 分支:秒级创建数据库分支
# 类似 Git 的分支概念,但用于数据库
# 创建分支(基于当前时间点)
neon branch create my-branch --parent main
# 创建分支(基于特定时间点)
neon branch create my-branch --parent main --timestamp "2026-06-15T10:00:00Z"
# 创建分支(基于特定 LSN)
neon branch create my-branch --parent main --lsn 0/1A2B3C4D
# 列出所有分支
neon branch list
# 删除分支
neon branch delete my-branch
# Neon 分支的工作原理
# 1. 分支共享父分支的存储(Copy-on-Write)
# 2. 新写入的数据独立存储
# 3. 读取时:先查自己的数据,再查父分支的数据
class NeonBranch:
def __init__(self, parent_lsn, page_server):
self.parent_lsn = parent_lsn # 分支点的 LSN
self.page_server = page_server
self.own_wal = [] # 分支自己的 WAL
def read_page(self, page_id):
"""读取页面:先查自己的 WAL,再查父分支"""
# 1. 在自己的 WAL 中查找
for record in reversed(self.own_wal):
if record.page_id == page_id:
return record.data
# 2. 在父分支中查找(LSN <= parent_lsn)
return self.page_server.get_page(page_id, self.parent_lsn)
def write_page(self, page_id, data):
"""写入页面:追加到自己的 WAL"""
self.own_wal.append(WalRecord(page_id, data))
Tip

Neon 的分支功能对开发流程有革命性影响:每个 PR 可以有独立的数据库分支,测试完成后删除。无需维护多套测试数据库,也无需担心测试数据污染。

3.4 Serverless 自动伸缩#

# Neon Serverless 配置
compute:
# 自动挂起:无流量时缩容到零
auto_suspend: 300 # 5 分钟无活动后挂起
# 自动恢复:有连接时自动启动
auto_resume: true
# 计算规格范围
min_cu: 0.25 # 最小 0.25 Compute Unit
max_cu: 8 # 最大 8 Compute Unit
# 冷启动时间
cold_start: ~500ms # 从挂起到可用
场景传统 RDSNeon Serverless
开发/测试24/7 运行,持续计费无流量时自动挂起,零计费
流量波动按峰值配置,资源浪费自动伸缩,按需付费
多租户每租户一个实例每租户一个分支,共享存储
冷启动分钟级亚秒级

四、共享存储模型#

4.1 存储后端对比#

存储类型代表延迟吞吐适用场景
本地 SSDNVMe SSD10-100 μs3-7 GB/s单机数据库
网络块存储AWS EBS gp31-5 ms1 GB/s传统云数据库
网络块存储(高性能)AWS EBS io20.5-2 ms4 GB/s高性能数据库
对象存储AWS S310-100 ms5 GB/s归档、备份
内存缓存ElastiCache0.1-1 ms10+ GB/s热数据缓存

4.2 本地缓存策略#

计算存储分离架构中,计算节点需要本地缓存来弥补共享存储的延迟:

// Aurora 本地缓存策略
type AuroraBufferPool struct {
shared *SharedStorage // 共享存储层
local *LRUCache // 本地 Buffer Pool
cacheSize int // 缓存大小
}
func (bp *AuroraBufferPool) ReadPage(pageID PageID) (*Page, error) {
// 1. 查本地缓存
if page, ok := bp.local.Get(pageID); ok {
return page, nil // 缓存命中,微秒级
}
// 2. 缓存未命中,从共享存储读取
page, err := bp.shared.ReadPage(pageID)
if err != nil {
return nil, err // 毫秒级延迟
}
// 3. 放入本地缓存
bp.local.Put(pageID, page)
return page, nil
}
func (bp *AuroraBufferPool) WritePage(pageID PageID, data []byte) error {
// 写入不需要立即写数据页!
// 只写 Redo Log 到共享存储
// 数据页在本地缓存中修改,后续异步刷回
bp.local.Put(pageID, NewPage(data))
return bp.shared.AppendLog(pageID, data)
}
缓存层级大小延迟命中率目标
L1 CPU Cache32-64 MB1-10 ns
L2 Buffer Pool1-128 GB100-500 ns> 99%
L3 本地 SSD 缓存100 GB - 1 TB10-100 μs> 95%
L4 共享存储PB 级1-5 ms

4.3 缓存一致性#

sequenceDiagram participant W as 写入节点 participant S as 共享存储 participant R as 只读节点 W->>S: 写 Redo Log (LSN=100) S-->>W: 确认 (4/6 副本) W->>W: 更新本地 Buffer Pool Note over R: 只读节点如何看到更新? R->>S: 读取页面 (请求 LSN >= 100) S-->>R: 返回最新页面 R->>R: 更新本地缓存 Note over R: 如果本地缓存是旧版本? R->>R: 检查页面 LSN < 请求 LSN R->>S: 请求最新版本 S-->>R: 返回 LSN=100 的页面
Warning

缓存一致性是计算存储分离的核心挑战。只读节点的本地缓存可能包含过期数据,需要通过 LSN 比较来检测和刷新。Aurora 使用”读修复”机制:读取时检查 LSN,如果本地版本过旧则从共享存储获取最新版本。

五、成本分析#

5.1 存储成本对比#

成本项传统 RDSAuroraNeon
计算节点1 写 + N 读1 写 + N 读按需 CU
存储费用N × 本地磁盘1 × 共享存储1 × 共享存储
I/O 费用包含在实例费按 I/O 请求计费包含在存储费
网络费用复制流量日志流量(小)WAL 流量(小)
备份费用快照存储自动增量备份S3 归档

5.2 总体拥有成本(TCO)#

场景:3 节点 MySQL 集群,1TB 数据
传统 RDS(3 × db.r5.xlarge):
计算:3 × $0.34/h = $734/月
存储:3 × 1TB × $0.115/GB = $345/月
总计:$1,079/月
Aurora(1 写 + 2 读):
计算:3 × $0.34/h = $734/月
存储:1 × 1TB × $0.10/GB = $100/月
I/O:~$50/月(估算)
总计:$884/月(节省 18%)
Neon Serverless:
计算:按需 ~$200/月(开发/测试场景)
存储:1TB × $0.12/GB = $120/月
总计:$320/月(节省 70%)
Note

成本节省因场景而异。生产环境(7×24 高负载)的节省较少,开发/测试环境(间歇性负载)的节省显著。Serverless 的优势在于按需付费,而不是绝对成本更低。

六、性能影响#

6.1 延迟分析#

操作本地存储共享存储增量
顺序写(WAL)0.1-0.5 ms1-3 ms2-6x
随机读(点查)0.05-0.2 ms0.5-2 ms5-10x
范围扫描0.01 ms/行0.05 ms/行5x
事务提交0.5-2 ms2-5 ms2-4x

6.2 延迟优化策略#

-- Aurora 性能优化
-- 1. 增大 Buffer Pool 提高缓存命中率
-- 参数:innodb_buffer_pool_size(建议 75% 可用内存)
-- 2. 使用 Aurora Global Database 减少跨区域延迟
aws rds create-global-cluster \
--global-cluster-identifier aurora-global \
--engine aurora-mysql
-- 3. 使用 Aurora Parallel Query 加速分析查询
SET aurora_parallel_query=ON;
SELECT /*+ PQ */ COUNT(*), SUM(amount)
FROM orders
WHERE created_at >= '2026-01-01';
# Neon 性能优化
# 1. 预热计算节点(减少冷启动)
neon compute start --project-id my-project
# 2. 调整 Page Server 缓存大小
# pageserver 配置
[pageserver]
page_cache_size = "4GB" # 页面缓存
max_file_descriptors = 8192 # 文件描述符
eviction_policy = "lru" # 淘汰策略
# 3. Safekeeper WAL 持久化配置
[safekeeper]
wal_keep_size = "1GB" # 保留的 WAL 大小
backup_parallelism = 4 # S3 上传并行度

七、Aurora vs Neon 对比#

维度AuroraNeon
兼容性MySQL / PostgreSQLPostgreSQL
开源否(AWS 专有)是(Apache 2.0)
存储模型6 副本 Quorum3 Safekeeper + S3
日志协议自定义 QuorumPaxos 变体
页面物化存储节点后台重放Page Server 按需物化
分支不支持支持(杀手级特性)
Serverless支持(Aurora Serverless v2)原生支持
冷启动~1 秒~500ms
最大存储128 TB无限制(S3)
只读副本最多 15 个无限制
部署AWS 专有任何云/本地

八、总结#

维度传统 RDSAuroraNeon
计算存储耦合分离分离
写入模型WAL + 数据页只写 WAL只写 WAL
存储冗余主从复制6 副本 Quorum3 Safekeeper + S3
只读副本异步复制共享存储共享存储
扩缩容分钟级秒级秒级
分支不支持不支持支持
Serverless不支持支持原生支持
开源部分
适用场景传统 OLTP云上 OLTP开发/测试/OLTP
Tip

计算存储分离不是银弹。它用网络延迟换取了弹性、可靠性和运维便利性。对于延迟极度敏感的场景(如高频交易),本地存储仍然是更好的选择。但对于绝大多数云上应用,计算存储分离带来的运维简化远超延迟的代价。

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

云原生存储
https://blog.souloss.com/posts/storage/storage-cloud-native/
作者
Souloss
发布于
2025-07-05
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时