四类服务的定位
DynamoDB
键值与文档存储,单表设计下可支撑千万级 QPS,延迟稳定在个位数毫秒。适合会话、订单状态、IoT 时序、游戏存档。
ElastiCache (Redis / Valkey)
内存缓存与数据结构服务。适合热点缓存、分布式锁、排行榜、限流计数、发布订阅。
DocumentDB
兼容 MongoDB 协议的托管文档数据库。适合从自建 MongoDB 迁移,或需要灵活 schema 的内容型业务。
OpenSearch
全文检索与日志分析。适合站内搜索、日志聚合、安全分析、向量检索(RAG 场景)。
DynamoDB 单表设计要点
DynamoDB 最大的认知门槛是:不要按关系型思维建多张表再 join,而是先列出所有访问模式,再反推分区键与排序键的设计,把相关数据放在同一个分区里。
常见做法是复合主键:PK 存实体类型 + ID(如 USER#1001),SK 存关系类型 + 时间戳(如 ORDER#2026-08-01#5566)。这样一次 Query 就能取出某用户的全部订单,无需 Scan。配合 GSI 支撑反向查询,比如按订单状态查所有待发货订单。
单表设计与访问模式示例
"""DynamoDB 单表设计示例:用户 - 订单 - 商品
访问模式(先定义清楚,再设计键):
AP1 按 userId 查用户资料
AP2 按 userId 查该用户的订单(按时间倒序,可分页)
AP3 按 orderId 查订单详情与明细
AP4 按订单状态查所有订单(跨用户)—— 用 GSI1
AP5 按 skuId 查该商品的销售记录 —— 用 GSI2
"""
import boto3
from datetime import datetime, timezone
from typing import Any
table = boto3.resource("dynamodb").Table("mushan-commerce")
def put_user(user_id: str, profile: dict[str, Any]) -> None:
"""AP1:用户主记录。"""
table.put_item(Item={
"PK": f"USER#{user_id}",
"SK": "PROFILE",
"entityType": "User",
**profile,
})
def put_order(user_id: str, order_id: str, status: str, amount: str) -> None:
"""AP2 / AP3 / AP4:订单头记录,同时写 GSI1 键。"""
now = datetime.now(timezone.utc).isoformat()
table.put_item(Item={
"PK": f"USER#{user_id}",
"SK": f"ORDER#{now}#{order_id}",
"entityType": "Order",
"orderId": order_id,
"status": status,
"amount": amount,
"createdAt": now,
# GSI1:按状态查订单
"GSI1PK": f"STATUS#{status}",
"GSI1SK": now,
})
def query_user_orders(user_id: str, limit: int = 20, cursor: dict | None = None):
"""AP2:查某用户订单,时间倒序分页。一次 Query 搞定,不用 Scan。"""
kwargs = {
"KeyConditionExpression": (
boto3.dynamodb.conditions.Key("PK").eq(f"USER#{user_id}")
& boto3.dynamodb.conditions.Key("SK").begins_with("ORDER#")
),
"ScanIndexForward": False, # 倒序,最新的在前
"Limit": limit,
}
if cursor:
kwargs["ExclusiveStartKey"] = cursor
resp = table.query(**kwargs)
return resp.get("Items", []), resp.get("LastEvaluatedKey")
def query_orders_by_status(status: str, limit: int = 50):
"""AP4:跨用户按状态查订单,走 GSI1。"""
resp = table.query(
IndexName="GSI1",
KeyConditionExpression=boto3.dynamodb.conditions.Key("GSI1PK").eq(f"STATUS#{status}"),
ScanIndexForward=False,
Limit=limit,
)
return resp.get("Items", [])
关键纪律:任何时候都不要在生产表上跑 Scan。发现需要 Scan,说明访问模式没被设计覆盖,该加 GSI 了。
缓存设计避坑清单
缓存一致性
- 写操作后主动失效缓存,而不是等 TTL 自然过期
- 缓存 key 加版本号或业务前缀,方便批量失效
- 允许短暂不一致的场景才用 Cache-Aside,强一致场景走数据库
- TTL 加随机抖动,避免大批 key 同时过期造成雪崩
可用性
- 开启集群模式与自动故障转移,至少 1 个副本
- 缓存不可用时业务能降级到数据库,而不是直接报错
- 热点 key 用本地缓存做二级缓冲,避免单节点被打穿
- 配置 maxmemory-policy,明确内存满了以后的淘汰策略
成本与监控
- 监控命中率,低于 80% 说明缓存策略需要重新设计
- 监控内存碎片率与逐出次数
- 用 Serverless 模式承接波动大的负载
- 定期扫描大 key 与慢命令