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│
└───────────┴──────────────┴─────────┴──────────┘

主键类型:

  1. 分区键(Partition Key)

    • 决定数据存储在哪个分区
    • 必须唯一(单键)
    • 示例:UserId
  2. 分区键 + 排序键(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 数据库,适合高并发、低延迟的现代应用!