DynamoDB NoSQL 数据库
Amazon DynamoDB 是完全托管的 NoSQL 键值和文档数据库。
核心概念
NoSQL vs 关系型数据库
关系型数据库(RDS)
├─ 固定 schema(表结构)
├─ ACID 事务
├─ SQL 查询语言
├─ 垂直扩展(升级实例)
├─ JOIN 查询
└─ 适合:复杂关系、事务一致性
NoSQL 数据库(DynamoDB)
├─ 灵活 schema
├─ 最终一致性(可选强一致性)
├─ 键值访问
├─ 水平扩展(分片)
├─ 无 JOIN(需要反范式设计)
└─ 适合:高并发、简单查询、海量数据
DynamoDB 数据模型
表(Table):
用户表示例:
┌───────────────┬──────────┬────────────────┐
│ UserId (PK) │ Name │ Email │
├───────────────┼──────────┼────────────────┤
│ user-001 │ Alice │ alice@ex.com │
│ user-002 │ Bob │ bob@ex.com │
└───────────────┴──────────┴────────────────┘
订单表示例(复合键):
┌───────────┬──────────────┬─────────┬──────────┐
│UserId(PK) │OrderId(SK) │ Amount │ Status │
├───────────┼──────────────┼─────────┼──────────┤
│ user-001 │ order-2024-01│ 99.99 │ Shipped │
│ user-001 │ order-2024-02│ 49.99 │ Pending │
│ user-002 │ order-2024-03│ 149.99 │ Delivered│
└───────────┴──────────────┴─────────┴──────────┘
主键类型:
分区键(Partition Key):
- 决定数据存储在哪个分区
- 必须唯一(单键)
- 示例:UserId
分区键 + 排序键(Sort Key):
- 复合主键
- 同一分区键下,排序键可排序
- 示例:UserId + Timestamp
数据分布
DynamoDB 分区机制:
┌─────────────────────────────────────────┐
│ DynamoDB 表 │
└──────────┬──────────┬────────────┬──────┘
│ │ │
┌────▼───┐ ┌───▼────┐ ┌────▼────┐
│Partition│Partition│ Partition│
│ 1 ││ 2 ││ 3 │
└────────┘ └────────┘ └─────────┘
Hash(PK1) Hash(PK2) Hash(PK3)
分区键的哈希值决定数据分布
- 均匀分布的分区键 = 更好的性能
- 热键(Hot Key)= 性能瓶颈
一致性模型
读取一致性
1. 最终一致性读取(Eventually Consistent):
特性:
├─ 默认模式
├─ 延迟:通常 < 1 秒
├─ 成本:0.5 读取单位(RCU)
└─ 适合:对实时性要求不高的场景
工作原理:
写入 → 主副本 → 异步复制 → 其他副本
↓
立即返回
读取可能返回旧数据(短暂)
2. 强一致性读取(Strongly Consistent):
特性:
├─ 显式指定
├─ 延迟:稍高
├─ 成本:1 读取单位(RCU)
└─ 适合:要求强一致性的场景
工作原理:
读取 → 主副本 → 返回最新数据
事务
ACID 事务支持:
TransactWriteItems(写事务)
├─ 最多 100 个项目
├─ 全部成功或全部失败
├─ 跨表支持
└─ 成本:2 倍 WCU
TransactGetItems(读事务)
├─ 最多 100 个项目
├─ 强一致性读取
└─ 成本:2 倍 RCU
使用场景:
- 转账操作(扣款 + 加款)
- 库存扣减(检查 + 更新)
- 订单创建(创建订单 + 扣库存)
容量模式
按需模式(On-Demand)
特性:
自动扩展
├─ 无需容量规划
├─ 自适应流量
├─ 按请求计费
│ ├─ 写入:$1.25/百万次
│ └─ 读取:$0.25/百万次
└─ 适合:不可预测的流量
何时选择:
- 新应用,流量未知
- 流量波动大
- 间歇性工作负载
- 不想管理容量
预配置模式(Provisioned)
特性:
固定容量
├─ 设置 RCU 和 WCU
│ ├─ 1 RCU = 4KB 强一致性读取/秒
│ ├─ 1 WCU = 1KB 写入/秒
│ └─ 最终一致性读取:0.5 RCU
├─ 按小时计费
│ ├─ $0.00013/WCU/小时
│ └─ $0.000025/RCU/小时
└─ 预留容量折扣(40-60%)
Auto Scaling:
自动调整容量
├─ 设置目标利用率(如 70%)
├─ 设置最小/最大容量
├─ 自动扩展和缩减
└─ 避免超额付费和限流
容量计算
读取容量单位(RCU):
计算公式:
RCU = (项目大小 / 4KB) × 读取次数/秒
示例 1:
- 项目大小:3KB
- 读取:100 次/秒
- 强一致性
RCU = ceil(3/4) × 100 = 100
示例 2:
- 项目大小:8KB
- 读取:50 次/秒
- 最终一致性
RCU = ceil(8/4) × 50 / 2 = 50
写入容量单位(WCU):
计算公式:
WCU = (项目大小 / 1KB) × 写入次数/秒
示例:
- 项目大小:2.5KB
- 写入:200 次/秒
WCU = ceil(2.5/1) × 200 = 600
查询模式
访问模式
1. GetItem(主键查询):
最快的访问方式
├─ 单次查询
├─ 延迟:个位数毫秒
└─ 1 RCU(4KB)
示例:
- 通过 UserId 获取用户信息
- 通过 UserId + OrderId 获取订单
2. Query(分区键 + 条件):
按分区键查询
├─ 可选排序键条件
├─ 可选过滤表达式
├─ 返回多个项目
└─ 最多返回 1MB
示例:
- 获取用户的所有订单
- 获取特定时间范围的订单
Query: UserId = 'user-001' AND Timestamp > 1640000000
3. Scan(全表扫描):
扫描整个表
├─ 消耗大量 RCU
├─ 可能需要多次扫描(分页)
├─ 性能差
└─ 应避免在生产环境使用
何时使用:
- 数据分析(离线)
- 数据导出
- 表很小(< 1000 项目)
二级索引
全局二级索引(GSI):
特点:
├─ 不同的分区键和排序键
├─ 独立的 RCU/WCU
├─ 最终一致性
├─ 最多 20 个 GSI
└─ 异步创建
示例:
主表:UserId (PK) + OrderId (SK)
GSI:Email (PK) + Timestamp (SK)
→ 通过邮箱查询用户订单
本地二级索引(LSI):
特点:
├─ 相同的分区键
├─ 不同的排序键
├─ 共享表的 RCU/WCU
├─ 支持强一致性
├─ 最多 5 个 LSI
└─ 创建表时定义(无法后续添加)
示例:
主表:UserId (PK) + OrderId (SK)
LSI:UserId (PK) + OrderDate (SK)
→ 按下单日期排序查询
索引设计最佳实践
1. 理解访问模式:
访问模式驱动设计
├─ 列出所有查询需求
├─ 确定分区键和排序键
├─ 设计必要的 GSI/LSI
└─ 避免过度索引
2. 避免热分区:
不好的分区键:
├─ Status(只有几个值)
├─ Date(同一天大量数据)
└─ Boolean(true/false)
好的分区键:
├─ UserId(均匀分布)
├─ UUID
└─ 复合分区键(Type + Id)
DynamoDB Streams
变更数据捕获
工作原理:
表变更 → DynamoDB Streams → 触发 Lambda
│
├─ INSERT(新项目)
├─ MODIFY(更新)
└─ REMOVE(删除)
│
▼
流记录保留 24 小时
视图类型:
- KEYS_ONLY:仅主键
- NEW_IMAGE:变更后的完整项目
- OLD_IMAGE:变更前的完整项目
- NEW_AND_OLD_IMAGES:变更前后都包含
使用场景:
实时应用:
├─ 数据复制(跨区域、跨账户)
├─ 数据聚合(实时统计)
├─ 审计日志
├─ 触发工作流
└─ 缓存失效
全局表
多区域复制
架构:
┌──────────────┐ 双向复制 ┌──────────────┐
│ us-east-1 │◄──────────────▶│ eu-west-1 │
│ (主区域) │ │ (副区域) │
└──────┬───────┘ └───────┬──────┘
│ │
│ 双向复制 │
│ │
┌──────▼───────┐ ┌───────▼──────┐
│ ap-southeast │◄──────────────▶│ │
│ -1 │ │ │
└──────────────┘ └──────────────┘
特性:
├─ 多主复制(Multi-Master)
├─ 最终一致性(通常 < 1 秒)
├─ 自动冲突解决(Last Writer Wins)
└─ 每个区域独立 RCU/WCU
使用场景:
- 全球化应用
- 低延迟访问
- 灾难恢复
- 合规要求(数据本地化)
备份和恢复
按需备份
特性:
完整备份
├─ 不影响性能
├─ 增量存储(成本优化)
├─ 长期保留
└─ 跨账户/跨区域恢复
时间点恢复(PITR)
特性:
连续备份
├─ 自动启用(可选)
├─ 保留 35 天
├─ 恢复到任意秒
├─ 恢复为新表
└─ 成本:$0.20/GB/月
性能优化
分区键设计
原则:
1. 均匀分布
- 避免热键
- 使用高基数属性
2. 访问模式优化
- 常用查询的分区键
- 考虑复合键
3. 时间序列数据
- 添加前缀分散数据
- 示例:${UserId}#${Date}
反模式(避免):
❌ 单一分区键(如 Status)
→ 热分区,性能瓶颈
❌ 自增 ID 作为分区键
→ 写入集中在一个分区
❌ 时间戳作为分区键
→ 最新数据热分区
批量操作
BatchGetItem:
- 一次最多 100 项
- 并行查询多个分区
- 减少往返次数
BatchWriteItem:
- 一次最多 25 项
- Put 或 Delete
- 部分失败可重试
成本优化
策略
1. 选择合适的容量模式:
流量模式分析:
稳定、可预测 → 预配置模式 + Auto Scaling
(成本更低)
不可预测、波动大 → 按需模式
(避免限流)
2. 使用预留容量:
- 1 年预留:~30% 折扣
- 3 年预留:~50% 折扣
- 适合稳定的生产负载
3. 优化数据模型:
成本优化技巧:
├─ 减小项目大小(压缩、简化)
├─ 使用 TTL 自动删除过期数据
├─ 合理使用索引(避免过度索引)
└─ 批量操作代替单项操作
4. 使用 DynamoDB Accelerator (DAX):
- 内存缓存
- 微秒级延迟
- 减少读取成本
- 适合读多的场景
最佳实践
1. 数据建模
单表设计:
优势:
├─ 减少跨表查询
├─ 事务支持更好
├─ 降低成本(减少索引)
└─ 提高性能
示例:
PK: EntityType#Id
SK: Metadata#Attribute
User#user-001 | Profile#Basic
User#user-001 | Order#order-001
User#user-001 | Order#order-002
Product#prod-01 | Details#Main
2. 错误处理
重试策略:
指数退避(Exponential Backoff)
├─ ProvisionedThroughputExceeded
├─ 首次重试:100ms 后
├─ 第二次:200ms 后
├─ 第三次:400ms 后
└─ 最多重试 5 次
3. 监控
关键指标:
- ConsumedReadCapacityUnits:实际 RCU 使用
- ConsumedWriteCapacityUnits:实际 WCU 使用
- UserErrors:客户端错误
- SystemErrors:服务端错误
- ThrottledRequests:被限流的请求
DynamoDB 是 AWS 上最快、最可扩展的 NoSQL 数据库,适合高并发、低延迟的现代应用!