云产品 · 数据库

高并发场景,关系型数据库不该是唯一选择

会话、排行榜、购物车、feed 流、全文检索,这些场景用对了 NoSQL 或缓存,能把延迟压到毫秒并大幅降低成本。

四类服务的定位

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 支撑反向查询,比如按订单状态查所有待发货订单。

单表设计与访问模式示例

python single_table_design.py
"""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 与慢命令

下一步

把云的复杂度交给我们,你只管做业务

留下需求,沐杉云的解决方案架构师会在一个工作日内联系你,提供免费的现状评估、迁移方案草案与 TCO 测算表。