请求与存储最优实践#


一、关于请求方式#

1. POST vs PUT 特性对比#

特性 POST PUT
语义 创建子资源,或提交数据供处理 替换整个目标资源(幂等)
幂等性 ❌ 不保证幂等(多次请求可能创建多个资源) ✅ 幂等(多次请求结果相同)
URI 通常由服务器决定最终 URI(如 /posts/posts/123 客户端明确指定完整 URI(如 /posts/123
常见用途 创建新资源、表单提交、部分更新(PATCH 之前常用) 全量更新已知资源、创建资源(若客户端指定 ID)

2. 示例对比#

操作 使用 POST 使用 PUT
创建新文章 ✅ 常见做法 也可以(客户端需自己生成 ID,如 /articles/uuid-123
全量更新文章 123 ❌ 可以但不推荐(不幂等) ✅ 标准做法
提交表单(购物车、联系表单)
上传大文件并覆盖已有文件 ❌(用 PUT)

3. 误区与补充#

  • POST 也能更新:REST 没有禁止 POST 做更新,但 PUT/PATCH 更语义明确
  • 部分更新:标准做法是用 PATCH,因为 PUT 要求全量替换(缺失字段会被删除)
  • PUT 创建资源:允许,但条件是客户端能提供唯一且最终不变的 URI
  • 部分 API 实践时,只开放 POST + PATCH,忽略 PUT
  • 幂等性对网络重试很重要(PUT 安全,POST 不安全)

快速判断#

需要客户端指定完整 URI + 幂等(可安全重试)+ 全量替换  →  PUT
URI 由服务器决定        + 可能改变状态/创建资源       →  POST
部分更新且幂等                                          →  PATCH

二、后端存储#

1. 核心原则#

原则 说明
数据安全优先 丢失比慢更致命
按需选择 没有万能存储,只有合适场景
分层存储 热/温/冷数据分离
不可变原则 能追加不修改,能软删不硬删
可观测性 慢查询、连接池、容量必须可监控

2. 数据库选型决策#

数据关系复杂?联查多?
├─ 是 → 关系型数据库(PostgreSQL / MySQL)
│        ├─ 强一致性 + 复杂事务 → PostgreSQL
│        └─ 读多写少 + 生态成熟 → MySQL

数据量大(TB级)?写入压力大?
├─ 是 → 分布式数据库
│        ├─ 强事务 + 水平扩展 → TiDB / CockroachDB
│        └─ 最终一致性 + 超高吞吐 → Cassandra / HBase

数据结构多变?无 schema?
├─ 是 → 文档数据库(MongoDB)
│        └─ 适合:日志、用户画像、CMS

需要全文搜索?
├─ 是 → Elasticsearch(配合主存储双写)

需要缓存 + 高性能?
├─ 是 → Redis
│        └─ 适合:会话、计数器、排行榜、分布式锁

需要图关系(社交、推荐)?
├─ 是 → 图数据库(Neo4j / Amazon Neptune)

时间序列数据(IoT、监控)?
├─ 是 → 时序数据库(InfluxDB / TimescaleDB / Prometheus)

3. 分层存储架构#

                    请求路径
                       │
                       ▼
┌──────────────────────────────────────────────────────┐
│                    L1: 缓存层                         │
│   Redis / Memcached                                   │
│   - TTL 5-15分钟                                       │
│   - 缓存穿透/击穿/雪崩防护                              │
└──────────────────────────────────────────────────────┘
                       │ 未命中
                       ▼
┌──────────────────────────────────────────────────────┐
│                    L2: 热存储                         │
│   PostgreSQL / MySQL(SSD,主从集群)                  │
│   - 最近 7-30 天数据                                   │
│   - 在线服务直接访问                                    │
└──────────────────────────────────────────────────────┘
                       │ 归档策略
                       ▼
┌──────────────────────────────────────────────────────┐
│                    L3: 温存储                         │
│   归档表 / 分区表 / 对象存储(OSS/S3)                  │
│   - 30 天 ~ 1 年数据                                   │
│   - 可离线分析                                          │
└──────────────────────────────────────────────────────┘
                       │ 长期保存
                       ▼
┌──────────────────────────────────────────────────────┐
│                    L4: 冷存储                         │
│   对象存储(Glacier Deep Archive)                     │
│   - 1 年以上数据                                       │
│   - 取回时间 12-48 小时                                │
└──────────────────────────────────────────────────────┘

4. 关系型最优实践#

表设计#

-- ✅ 推荐:软删除 + 审计字段
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    status SMALLINT DEFAULT 1,           -- 1:正常 2:禁用 3:已删
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    deleted_at TIMESTAMPTZ,              -- NULL = 未删除
    version INT DEFAULT 1                -- 乐观锁
);

-- ✅ 使用 UUID 作为业务 ID(防遍历/分库兼容)
-- ❌ 避免用自增 ID 暴露业务量

索引策略#

场景 建议
等值查询(WHERE email = ? B-Tree 索引
范围查询(WHERE created_at > ? B-Tree 索引 + 排序方向一致
模糊查询(LIKE 'abc%' 普通索引(前置通配符 %abc 用不到索引)
全文搜索 倒排索引(PostgreSQL tsvector / Elasticsearch)
多条件排序 联合索引(覆盖排序字段)
JSON 字段查询 GIN 索引(PostgreSQL)

索引黄金法则

  • 每个表 ≤ 5 个索引
  • 重复/冗余索引果断删除
  • EXPLAIN 验证执行计划
  • 监控索引命中率(未使用索引 > 30 天 → 删除)

防慢查询#

-- 设置语句超时
SET statement_timeout = '5s';

-- ❌ 避免
SELECT * FROM orders;                           -- 全表扫描
SELECT COUNT(*) FROM huge_table;                -- 大数据量计数
WHERE name LIKE '%keyword%';                    -- 左通配符
WHERE YEAR(created_at) = 2025;                  -- 函数破坏索引

-- ✅ 推荐
SELECT id, email, status FROM orders LIMIT 100; -- 限制字段 + 分页
WHERE created_at BETWEEN '2025-01-01' AND '2025-12-31';

5. Redis 缓存最优实践#

数据结构选择#

业务场景 推荐结构 示例
缓存单值 String user:123
对象缓存 Hash user:123 多个字段
排行榜 Sorted Set 积分榜
去重集合 Set 今日访问 IP
消息队列 Stream / List 轻量级队列
计数器 String + INCR PV/UV
分布式锁 String + SET NX EX Redlock

缓存三大问题(穿透、击穿、雪崩)#

# 1. 缓存穿透(查不存在的数据)
# 解决:布隆过滤器 或 缓存空值(TTL 短)
if not bloom_filter.might_contain(key):
    return None
value = cache.get(key)
if value == "NULL":
    return None

# 2. 缓存击穿(热点 key 失效)
# 解决:互斥锁 / 逻辑过期
value = cache.get(key)
if value is None:
    with redis_lock(f"lock:{key}"):
        value = db.query()
        cache.set(key, value, ttl=300)

# 3. 缓存雪崩(大量 key 同时失效)
# 解决:TTL 加随机值
ttl = 3600 + random.randint(0, 300)

内存控制#

# 设置最大内存
maxmemory 4gb

# 淘汰策略(选其一)
maxmemory-policy allkeys-lru     # 最常用
# volatile-lru    # 仅对有过期时间的 key
# allkeys-random  # 不推荐
# noeviction      # 内存满时拒绝写入

6. 写入与事务#

事务边界#

# ✅ 推荐:短事务
@transaction.atomic
def create_order(user_id, items):
    # 1. 创建订单(1条 INSERT)
    order = Order.objects.create(user_id=user_id)
    # 2. 批量插入订单项(N条 INSERT)
    OrderItem.objects.bulk_create(items)
    # 3. 扣减库存(1条 UPDATE)
    # 事务总耗时 < 100ms

# ❌ 避免:长事务
@transaction.atomic
def bad_operation():
    # 1. 调用外部 API(可能 5 秒)
    # 2. 发送邮件(可能 2 秒)
    # 3. 更新数据库(1 条 UPDATE)
    # → 持有锁 7 秒,阻塞其他请求

写入优化#

场景 方案
批量写入 bulk_insert 分批(每批 500-1000 条)
高并发扣减 Redis 计数 + 异步同步 DB
日志/埋点 先写本地队列,异步批量写入
避免锁竞争 乐观锁(version 字段)优于悲观锁
大字段更新 分离到独立表(如 JSON 配置)

7. 高可用与备份#

           ┌─────────────┐
           │  负载均衡   │
           └──────┬──────┘
                  │
    ┌─────────────┼─────────────┐
    │             │             │
┌───▼───┐     ┌───▼───┐     ┌───▼───┐
│ Master│ ◄─► │ Slave │     │Slave  │  ← 读写分离
└───┬───┘     └───────┘     └───────┘
    │
    │ 异步复制 / 半同步
    ▼
┌───────────┐
│  Backup   │  ← 每天全量 + WAL 归档
└───────────┘

备份策略#

备份类型 频率 保留期 恢复时间目标
全量备份 每天 1 次 30 天 2-4 小时
增量/WAL 每 15-30 分钟 7 天 30 分钟
逻辑导出 每周 90 天 取决于大小

⚠️ 验收备份:每月至少一次恢复演练

监控指标#

维度 关键指标 告警阈值
连接数 活跃连接 / 最大连接 > 80%
慢查询 慢查询数量/QPS 每分钟 > 10 条
磁盘 使用率 > 80%
复制延迟 主从延迟秒数 > 10 秒
缓存命中率 命中数 / 总请求 < 90%(业务相关)
事务 平均时长 > 500ms

8. 常见反模式#

反模式 正确做法
存储 JSON 大字段并频繁更新 拆表或用文档数据库
把所有存储放一个数据库 读写分离 + 分库分表
不用连接池(每次新建连接) 连接池复用
索引滥加(10+ 索引/表) 定期清理未使用索引
SELECT * 只取需要的字段
在循环中查数据库(N+1 问题) 批量查询或预加载
依赖自增 ID 做业务逻辑 用 UUID/雪花 ID
生产环境直接修改表结构 在线 DDL 工具(gh-ost/pt-osc)

9. 决策速查#

需求 方案
需要强 ACID 事务? → PostgreSQL / MySQL
需要极高读写(10w+ QPS)? → Redis(缓存)+ 分库分表
数据量 > 1TB? → 分布式(TiDB / Cassandra)
查询模式不可预知? → MongoDB
需要全文搜索? → Elasticsearch + 主存储
只有几个简单键值对? → Redis 就够了
日志/监控指标? → 时序数据库 + 对象存储归档