缓存:Redis vs Memcached
应用 → Cache(先查)→ DB(缓存没有再查)
| 维度 | Redis | Memcached |
|---|---|---|
| 数据结构 | 字符串 / 列表 / 哈希 / 集合 / Sorted Set / Stream / Pub-Sub | 仅字符串(key/value) |
| 持久化 | 支持(AOF / RDB) | 不支持 |
| 集群 | Redis Cluster / Sentinel | 客户端分片 |
| 内存效率 | 中 | 高 |
| 适合 | 多用途,"瑞士军刀" | 纯 KV 缓存,规模超大 |
2026 主流是 Redis——除非你是特别大规模的纯 KV 场景,Memcached 不再是首选。
注:Redis 7.4 起改用 SSPL/RSALv2 双许可,社区分叉出 Valkey(Linux Foundation)和 KeyDB。新项目可以考虑 Valkey。
缓存使用模式
Cache-Aside(最常见)
def get_user(uid):
val = cache.get(f"user:{uid}")
if val:
return val
val = db.query(uid)
cache.set(f"user:{uid}", val, ttl=300)
return val
应用控制读写——简单灵活。
Write-Through
def update_user(uid, data):
db.update(uid, data)
cache.set(f"user:{uid}", data) # 同时更新缓存
写时同步缓存——读永远新鲜,但写更慢。
Write-Behind / Cache Invalidation
更复杂,少用。
缓存的几个老坑
- 缓存穿透:查不存在的 key → 永远落到 DB → DDoS。对策:缓存空结果(用短 TTL)
- 缓存雪崩:大量 key 同时过期 → DB 一瞬间被打挂。对策:TTL 加随机抖动
- 缓存击穿:热点 key 过期那一刻被多个请求同时打 DB。对策:互斥锁 / 异步重建
- 数据不一致:DB 改了缓存没改 → 取到旧值。对策:写时 invalidate 缓存(不是 update)
消息队列:核心场景
生产者 → MQ → 消费者
什么时候真的需要 MQ:
- 解耦:生产者不等消费者,各自伸缩
- 削峰:流量瞬时爆 → 进队列慢慢消费
- 重试 / 异步:邮件 / 短信 / 长任务,失败可重试
- 事件驱动:一处发生变化,多处消费
主流选型
| MQ | 模型 | 主要特点 |
|---|---|---|
| Apache Kafka | 日志(pull) | 高吞吐、长保留、分区、流处理生态 |
| RabbitMQ | 经典队列(AMQP) | 灵活路由(exchange / binding)、低延迟 |
| NATS / NATS JetStream | 发布订阅 + 持久流 | 极轻量、低延迟、云原生 |
| Redis Streams | 流(pull) | 已有 Redis 时附加功能 |
| AWS SQS / SNS | 托管队列 / Pub-Sub | 完全托管,按量计费 |
| GCP Pub/Sub | Pub-Sub | 全球发布 |
| 阿里 RocketMQ / 腾讯 CMQ / 华为 DMS | 类 Kafka | 国内同等场景 |
| Apache Pulsar | 分布式流 | 多租户、计算存储分离 |
决策矩阵(一种思路)
| 你的需求 | 推荐 |
|---|---|
| 简单异步任务(邮件 / 后台处理) | Redis Queue / SQS / RabbitMQ |
| 高吞吐数据流(日志 / 事件) | Kafka |
| 服务间事件总线(微服务事件驱动) | NATS / Kafka / Redis Streams |
| 路由复杂(按 key fan-out / 优先级队列) | RabbitMQ |
| 不想运维 + 不大不小规模 | 云商托管(SQS / GCP Pub/Sub) |
| 已经有 Redis | Redis Streams / Queue(先用,规模真大再上 Kafka) |
Kafka 的常被低估的复杂度
Kafka 强大但运维负担重:
- 需要 ZooKeeper(旧版本)/ KRaft(新版)
- 分区 + 复制因子要规划
- 消费者位移管理
- Schema Registry(Avro / Protobuf)
- 监控、扩容、磁盘管理
没有专职团队前,用托管 Kafka(Confluent Cloud / AWS MSK / 阿里 / 腾讯)。
消息系统的几个原则
1. 至少一次 ≠ 一次
绝大多数 MQ 提供至少一次(at-least-once)——可能重复。消费者必须幂等。
def process(msg):
if already_processed(msg.id):
return
do_work(msg)
mark_processed(msg.id)
2. 顺序仅在分区内保证
Kafka 同分区有序,跨分区无序。有序性靠分区 key 划分。
3. Dead-letter Queue(DLQ)
反复处理失败的消息 → 转到 DLQ → 人工处理。没有 DLQ 的队列会被毒消息卡死。
4. 监控滞后(Consumer Lag)
消费跟不上生产 → 队列堆积。Lag 必须有告警。
反模式
- MQ 当数据库用:MQ 是"事件流",不适合点查
- 小项目就上 Kafka:3 服务上 Kafka = 杀鸡用牛刀 + 运维负担
- 缓存当真相:DB 才是真相,缓存可以丢;反过来则灾难
- 消息没幂等性,靠 ack:网络抖动 → 重复消费 → 数据错
推荐阅读
- Redis 官方文档 / Valkey 官方 — Redis fork,新项目可考虑
- Apache Kafka 官方文档
- The Log: What every software engineer should know — Jay Kreps(Kafka 作者)的经典文章
- Designing Data-Intensive Applications — Ch 11 流处理
- NATS 官方文档 — 轻量 MQ 的另一选择
下一篇是这一系列的最后一篇——容量规划 + FinOps 入门。