后端面试题 - 缓存与消息队列篇

3 阅读23分钟

一、缓存系统

1.1 缓存基础理论

Q1: 什么是缓存?为什么需要缓存?常见的缓存策略有哪些?

答案:

缓存的本质: 缓存是一种用空间换时间的技术,将频繁访问的数据存储在访问速度更快的介质中,减少对慢速存储的访问。

为什么需要缓存?

性能对比:

不同存储介质的访问延迟:

L1 Cache (CPU一级缓存)    : 1 纳秒
L2 Cache (CPU二级缓存)    : 4 纳秒
L3 Cache (CPU三级缓存)    : 10 纳秒
内存 (RAM)                : 100 纳秒
SSD (固态硬盘)            : 10-100 微秒 (10,000-100,000纳秒)
HDD (机械硬盘)            : 1-10 毫秒 (1,000,000-10,000,000纳秒)
网络 (同机房)             : 0.5 毫秒
网络 (跨地域)             : 50-100 毫秒

结论:
- 内存比SSD快100-1000倍
- 内存比HDD快10,000-100,000倍
- 内存比跨地域网络快500,000-1,000,000倍

实际业务场景:

场景1: 电商商品详情页

无缓存方案:
1. 查询商品基本信息 (products表)
2. 查询库存信息 (stock表)
3. 查询价格信息 (price表)
4. 查询商品图片 (images表)
5. 查询用户评论 (reviews表)
6. 聚合数据返回

总耗时: 5次SQL查询 × 10ms = 50ms
QPS上限: 1000ms / 50ms = 20 QPS/核

有缓存方案:
1. 查询Redis缓存 (一次网络请求)
2. 缓存命中,直接返回

总耗时: 1ms
QPS上限: 1000ms / 1ms = 1000 QPS/核

性能提升: 50倍!

场景2: 社交网络Feed流

问题: 用户刷首页,需要查询1000个好友的最新动态

无缓存方案:
SELECT * FROM posts
WHERE user_id IN (1,2,3,...,1000)
ORDER BY create_time DESC
LIMIT 20;

问题:
1. IN条件包含1000个ID,查询慢
2. 每个用户的好友列表不同,无法复用
3. 数据库压力巨大

有缓存方案:
1. 每个用户的Feed流预先计算好,存入Redis
2. 新帖子发布时,推送到粉丝的Feed缓存
3. 用户刷新时直接从Redis读取

性能: 从数据库100ms → Redis 1ms,提升100倍

常见的缓存策略:

1. Cache-Aside (旁路缓存) - 最常用

读流程:

1. 应用先查询缓存
2. 缓存命中 → 直接返回
3. 缓存未命中 → 查询数据库
4. 将数据库结果写入缓存
5. 返回结果

代码示例:
def get_user(user_id):
    # 1. 查询缓存
    user = cache.get(f"user:{user_id}")
    if user:
        return user  # 缓存命中

    # 2. 缓存未命中,查询数据库
    user = db.query("SELECT * FROM users WHERE id = ?", user_id)

    # 3. 写入缓存
    cache.set(f"user:{user_id}", user, ttl=3600)

    return user

写流程:

1. 更新数据库
2. 删除缓存 (不是更新缓存!)

代码示例:
def update_user(user_id, new_data):
    # 1. 更新数据库
    db.execute("UPDATE users SET name = ? WHERE id = ?", new_data, user_id)

    # 2. 删除缓存 (让下次读取时重新加载)
    cache.delete(f"user:{user_id}")

为什么是删除而不是更新缓存?

场景: 并发更新用户余额

时间线:
T1: UPDATE users SET balance = 100 WHERE id = 1
T2: UPDATE users SET balance = 200 WHERE id = 1
T1: cache.set("user:1", balance=100)  -- 旧值覆盖新值!
T2: cache.set("user:1", balance=200)

问题: 缓存和数据库不一致!

正确做法:
T1: UPDATE users SET balance = 100 WHERE id = 1
T2: UPDATE users SET balance = 200 WHERE id = 1
T1: cache.delete("user:1")
T2: cache.delete("user:1")
下次读取时从数据库加载最新值200

2. Read-Through (读穿透)

特点: 缓存作为代理,应用不直接访问数据库

应用 → 缓存 → 数据库

读流程:
1. 应用查询缓存
2. 缓存命中 → 返回
3. 缓存未命中 → 缓存自动查询数据库,填充缓存,返回结果

代码示例 (伪代码):
class ReadThroughCache:
    def get(self, key):
        value = self.cache.get(key)
        if value:
            return value

        # 缓存自动加载数据
        value = self.load_from_db(key)
        self.cache.set(key, value)
        return value

3. Write-Through (写穿透)

特点: 写操作同时更新缓存和数据库

写流程:
1. 应用写入缓存
2. 缓存同步写入数据库
3. 两者都成功才返回

代码示例:
class WriteThroughCache:
    def set(self, key, value):
        # 1. 先写数据库
        self.save_to_db(key, value)

        # 2. 再写缓存
        self.cache.set(key, value)

优点: 缓存和数据库始终一致
缺点: 写入性能差 (需要等待数据库)

4. Write-Behind (写回,异步写)

特点: 先写缓存,异步批量写数据库

写流程:
1. 应用写入缓存,立即返回
2. 后台线程定时将缓存数据批量写入数据库

代码示例:
class WriteBehindCache:
    def __init__(self):
        self.dirty_keys = set()
        self.start_flush_thread()

    def set(self, key, value):
        # 1. 写入缓存
        self.cache.set(key, value)

        # 2. 标记为脏数据
        self.dirty_keys.add(key)

    def flush_thread(self):
        while True:
            time.sleep(10)  # 每10秒刷新一次
            for key in self.dirty_keys:
                value = self.cache.get(key)
                self.save_to_db(key, value)
            self.dirty_keys.clear()

优点: 写入性能极高,可以批量合并写操作
缺点: 可能丢失数据 (缓存宕机但未刷新到数据库)

应用场景: 日志系统、点击计数、浏览统计等允许少量数据丢失的场景

5. Write-Around (绕写)

特点: 写操作直接写数据库,不更新缓存

写流程:
1. 应用写入数据库
2. 不更新缓存 (让缓存自然过期)

读流程:
1. 查询缓存
2. 缓存未命中 → 查询数据库 → 填充缓存

优点: 适合写多读少的场景,避免缓存污染
缺点: 第一次读会慢 (缓存未命中)

缓存策略对比:

策略一致性性能复杂度适用场景
Cache-Aside大部分场景 (推荐)
Read-Through读多写少
Write-Through强一致性要求
Write-Behind极高允许少量数据丢失
Write-Around写多读少

如何选择缓存策略?

读多写少 (商品详情、用户信息)
→ Cache-Aside

强一致性 (库存、余额)
→ Write-Through 或不使用缓存

高并发写入 (浏览量、点赞数)
→ Write-Behind

写后很久才读 (日志、归档数据)
→ Write-Around

1.2 缓存常见问题

Q2: 什么是缓存穿透、缓存击穿、缓存雪崩?如何解决?

答案:

1. 缓存穿透 (Cache Penetration)

定义: 查询一个数据库中不存在的数据,导致每次请求都穿透缓存,直接访问数据库。

攻击场景:

场景: 电商网站商品详情接口

正常请求:
GET /api/product/123  → 缓存或数据库有数据

恶意请求:
GET /api/product/-1
GET /api/product/999999999
GET /api/product/abc

流程:
1. 查询缓存 → 不存在
2. 查询数据库 → 不存在
3. 返回空结果,不缓存
4. 下次相同请求重复上述流程

危害:
- 每秒10万个恶意请求
- 全部穿透到数据库
- 数据库CPU 100%,连接池耗尽
- 整个系统瘫痪!

解决方案1: 缓存空对象

def get_product(product_id):
    # 1. 查询缓存
    cache_key = f"product:{product_id}"
    product = cache.get(cache_key)

    if product == "NULL":  # 缓存的空值
        return None

    if product:
        return product

    # 2. 查询数据库
    product = db.query("SELECT * FROM products WHERE id = ?", product_id)

    if product:
        # 3. 缓存存在的数据
        cache.set(cache_key, product, ttl=3600)
    else:
        # 4. 缓存空值,设置较短的过期时间
        cache.set(cache_key, "NULL", ttl=60)  # 60秒后过期

    return product

优点: 简单有效
缺点: 占用缓存空间,需要设置合理的过期时间

解决方案2: 布隆过滤器 (Bloom Filter)

from pybloom import BloomFilter

# 初始化布隆过滤器
bf = BloomFilter(capacity=1000000, error_rate=0.001)

# 将所有商品ID加入布隆过滤器
for product_id in db.query("SELECT id FROM products"):
    bf.add(str(product_id))

def get_product(product_id):
    # 1. 先判断布隆过滤器
    if str(product_id) not in bf:
        return None  # 一定不存在,直接返回

    # 2. 可能存在,查询缓存
    product = cache.get(f"product:{product_id}")
    if product:
        return product

    # 3. 查询数据库
    product = db.query("SELECT * FROM products WHERE id = ?", product_id)
    if product:
        cache.set(f"product:{product_id}", product, ttl=3600)

    return product

优点:
- 空间效率高 (100万数据仅占1.2MB)
- 时间复杂度O(1)
- 完全阻挡不存在的请求

缺点:
- 存在误判 (0.1%的概率,可调整)
- 不支持删除元素 (可用Counting Bloom Filter)
- 新增商品需要更新布隆过滤器

解决方案3: 参数校验

def get_product(product_id):
    # 1. 参数校验
    if not product_id or not str(product_id).isdigit():
        raise ValueError("Invalid product_id")

    if int(product_id) <= 0 or int(product_id) > 10000000:
        raise ValueError("product_id out of range")

    # 2. 正常查询逻辑
    ...

优点: 在入口处拦截非法请求
缺点: 无法处理合法但不存在的ID

解决方案4: 限流熔断

from ratelimit import limits

@limits(calls=100, period=1)  # 每秒最多100次请求
def get_product(product_id):
    ...

优点: 防止恶意攻击
缺点: 可能影响正常用户

2. 缓存击穿 (Cache Breakdown / Hotspot Invalid)

定义: 热点数据的缓存过期,在过期瞬间大量并发请求穿透到数据库。

攻击场景:

场景: 明星离婚新闻上热搜

正常情况:
- 新闻ID=888的访问量: 1000万/秒
- 缓存有效,所有请求由Redis承载

缓存过期瞬间:
- 12:00:00 缓存过期
- 12:00:00.001 同时来了10万个请求
- 10万个请求都查询数据库
- 数据库瞬间打满,连接数耗尽
- 系统雪崩!

时间线:
12:00:00.000 - 缓存过期
12:00:00.001 - 请求1: 缓存未命中 → 查询数据库
12:00:00.001 - 请求2: 缓存未命中 → 查询数据库
12:00:00.001 - 请求3: 缓存未命中 → 查询数据库
...
12:00:00.001 - 请求100000: 缓存未命中 → 查询数据库
12:00:00.050 - 请求1: 数据库返回 → 写入缓存
12:00:00.050 - 后续请求命中缓存

问题: 这50毫秒内,10万个请求都打到了数据库!

解决方案1: 互斥锁 (Mutex Lock)

import redis
import time

def get_news(news_id):
    cache_key = f"news:{news_id}"
    lock_key = f"lock:news:{news_id}"

    # 1. 查询缓存
    news = cache.get(cache_key)
    if news:
        return news

    # 2. 缓存未命中,尝试获取锁
    lock = redis_client.set(lock_key, 1, nx=True, ex=10)  # 10秒过期的互斥锁

    if lock:
        # 2.1 获取锁成功,查询数据库
        try:
            news = db.query("SELECT * FROM news WHERE id = ?", news_id)
            cache.set(cache_key, news, ttl=3600)
            return news
        finally:
            # 2.2 释放锁
            redis_client.delete(lock_key)
    else:
        # 2.3 获取锁失败,等待并重试
        time.sleep(0.05)  # 等待50毫秒
        return get_news(news_id)  # 重试

流程分析:
请求1: 缓存未命中 → 获取锁成功 → 查询数据库 → 写缓存 → 释放锁
请求2-100000: 缓存未命中 → 获取锁失败 → 等待 → 重试 → 缓存命中

优点: 只有一个请求查询数据库,其他请求等待
缺点: 等待时间可能较长,用户体验差

解决方案2: 逻辑过期 (永不过期)

import json
import time

def get_news(news_id):
    cache_key = f"news:{news_id}"

    # 1. 查询缓存
    cached_data = cache.get(cache_key)
    if not cached_data:
        # 首次加载
        news = db.query("SELECT * FROM news WHERE id = ?", news_id)
        data = {
            "value": news,
            "expire_time": int(time.time()) + 3600  # 逻辑过期时间
        }
        cache.set(cache_key, json.dumps(data))  # Redis键永不过期
        return news

    # 2. 解析缓存数据
    data = json.loads(cached_data)
    expire_time = data["expire_time"]

    # 3. 判断逻辑过期
    if int(time.time()) < expire_time:
        # 未过期,直接返回
        return data["value"]

    # 4. 已过期,异步更新
    lock_key = f"lock:news:{news_id}"
    lock = redis_client.set(lock_key, 1, nx=True, ex=10)

    if lock:
        # 获取锁成功,异步更新缓存
        threading.Thread(target=async_update_cache, args=(news_id,)).start()

    # 5. 先返回旧数据 (不阻塞用户)
    return data["value"]

def async_update_cache(news_id):
    """异步更新缓存"""
    news = db.query("SELECT * FROM news WHERE id = ?", news_id)
    data = {
        "value": news,
        "expire_time": int(time.time()) + 3600
    }
    cache.set(f"news:{news_id}", json.dumps(data))

优点: 用户永远不会等待,体验好
缺点: 短时间内可能返回旧数据,存在最终一致性问题

解决方案3: 多级缓存

架构:
浏览器缓存 → CDN → Nginx本地缓存 → Redis → 数据库

即使Redis过期,Nginx本地缓存仍可承载部分流量

配置示例 (Nginx):
location /api/news {
    proxy_cache my_cache;
    proxy_cache_valid 200 10m;  # 成功响应缓存10分钟
    proxy_cache_key "$request_uri";
    proxy_pass http://backend;
}

3. 缓存雪崩 (Cache Avalanche)

定义: 大量缓存同时过期,导致所有请求瞬间打到数据库。

场景:

问题代码:
# 系统启动时,一次性加载所有热点数据,设置相同的过期时间
for product_id in hot_products:
    product = db.query("SELECT * FROM products WHERE id = ?", product_id)
    cache.set(f"product:{product_id}", product, ttl=3600)  # 都是3600秒

问题:
- 1小时后,10万个缓存同时过期
- 下一秒,10万个请求同时查询数据库
- 数据库瞬间崩溃!

时间线:
09:00:00 - 系统启动,加载10万个缓存,过期时间都是10:00:00
10:00:00 - 10万个缓存同时过期
10:00:01 - 新请求到来,缓存全部未命中,10万个请求打到数据库
10:00:01 - 数据库连接池耗尽,查询超时,服务雪崩!

解决方案1: 过期时间随机化

import random

def set_cache_with_random_ttl(key, value, base_ttl=3600):
    # 在基础TTL上增加随机抖动
    random_ttl = base_ttl + random.randint(0, 600)  # 增加0-10分钟的随机时间
    cache.set(key, value, ttl=random_ttl)

# 批量加载缓存
for product_id in hot_products:
    product = db.query("SELECT * FROM products WHERE id = ?", product_id)
    set_cache_with_random_ttl(f"product:{product_id}", product, base_ttl=3600)

效果:
- 缓存过期时间分散在3600-4200秒
- 每秒只有一小部分缓存过期
- 数据库压力平滑

解决方案2: 多级缓存架构

L1缓存 (本地内存): 容量小,速度极快,过期时间短 (1分钟)
L2缓存 (Redis):    容量大,速度快,过期时间长 (1小时)
L3数据库:          容量无限,速度慢

查询流程:
1. 查询L1缓存 → 命中返回
2. 查询L2缓存 → 命中返回,并写入L1
3. 查询数据库 → 返回,并写入L2和L1

优势:
- 即使L2缓存雪崩,L1仍能承载大部分流量
- 减少对数据库的冲击

解决方案3: 缓存预热 + 后台定时刷新

import schedule
import time

def warm_up_cache():
    """缓存预热"""
    hot_products = db.query("SELECT id FROM products WHERE is_hot = 1")
    for product_id in hot_products:
        refresh_product_cache(product_id)

def refresh_product_cache(product_id):
    """刷新单个商品缓存"""
    product = db.query("SELECT * FROM products WHERE id = ?", product_id)
    cache.set(f"product:{product_id}", product, ttl=7200)  # 2小时

# 每小时刷新一次缓存 (在缓存过期前刷新)
schedule.every(1).hours.do(warm_up_cache)

while True:
    schedule.run_pending()
    time.sleep(60)

优势:
- 缓存永远不会真正过期
- 刷新过程在后台进行,不影响用户

解决方案4: 限流降级

from ratelimit import limits

@limits(calls=1000, period=1)  # 数据库查询限流
def query_db_with_limit(sql, *args):
    return db.query(sql, *args)

def get_product(product_id):
    product = cache.get(f"product:{product_id}")
    if product:
        return product

    try:
        # 限流查询数据库
        product = query_db_with_limit("SELECT * FROM products WHERE id = ?", product_id)
        cache.set(f"product:{product_id}", product, ttl=3600)
        return product
    except RateLimitException:
        # 触发限流,返回降级数据
        return get_fallback_product(product_id)

def get_fallback_product(product_id):
    """降级方案: 返回静态数据或默认值"""
    return {
        "id": product_id,
        "name": "商品加载中...",
        "price": 0,
        "stock": 0
    }

优势:
- 保护数据库,防止雪崩
- 用户体验降级但不完全不可用

三大问题对比总结:

问题原因特征解决方案
缓存穿透查询不存在的数据缓存和数据库都没有布隆过滤器、缓存空值
缓存击穿热点数据过期单个key高并发互斥锁、逻辑过期
缓存雪崩大量缓存同时过期大量key同时失效过期时间随机化、多级缓存

1.3 缓存一致性问题

Q3: 如何保证缓存和数据库的一致性?

答案:

一致性问题的根源: 缓存和数据库是两个独立的存储系统,更新操作无法保证原子性,导致数据不一致。

常见的一致性问题:

问题1: 先更新数据库,再删除缓存

时间线:
T1: UPDATE users SET name = 'Alice' WHERE id = 1  -- 数据库name=Alice
T2: 系统宕机,缓存删除失败
    缓存: name=Bob (旧值)
    数据库: name=Alice (新值)
    → 数据不一致!

持续时间: 直到缓存过期 (可能数小时)

问题2: 先删除缓存,再更新数据库

时间线:
T1: cache.delete("user:1")                        -- 删除缓存
T2: SELECT * FROM users WHERE id = 1              -- 并发读请求,缓存未命中
T2: cache.set("user:1", name=Bob)                 -- 将旧值写入缓存
T1: UPDATE users SET name = 'Alice' WHERE id = 1  -- 更新数据库

结果:
    缓存: name=Bob (旧值)
    数据库: name=Alice (新值)
    → 数据不一致!

持续时间: 直到缓存过期

问题3: 先更新缓存,再更新数据库

时间线:
T1: cache.set("user:1", name=Alice)               -- 更新缓存
T1: UPDATE users SET name = 'Alice' WHERE id = 1  -- 更新数据库失败(数据库宕机)

结果:
    缓存: name=Alice (新值)
    数据库: name=Bob (旧值)
    → 数据不一致!

危害: 后续读取都是错误的新值,数据库旧值丢失

解决方案对比:

方案1: Cache-Aside + 延迟双删 (推荐)

def update_user(user_id, new_name):
    # 1. 删除缓存
    cache.delete(f"user:{user_id}")

    # 2. 更新数据库
    db.execute("UPDATE users SET name = ? WHERE id = ?", new_name, user_id)

    # 3. 延迟删除缓存 (解决并发读问题)
    time.sleep(0.5)  # 等待并发读请求完成
    cache.delete(f"user:{user_id}")

原理:
- 第一次删除: 删除旧缓存
- 第二次删除: 删除并发读写入的旧缓存
- 延迟时间: 大于主从复制延迟 + 读请求耗时

问题:
- 延迟时间不好估算
- 仍可能失败 (第二次删除失败)

方案2: 订阅Binlog异步删除缓存 (最佳)

架构:
MySQL BinlogCanal/MaxwellMQ → 缓存删除服务 → Redis

流程:
1. 应用更新数据库
2. Canal监听MySQL Binlog
3. 解析Binlog,提取变更数据
4. 发送到Kafka消息队列
5. 缓存删除服务消费消息,删除缓存

代码示例 (Canal):
public class CacheInvalidateHandler extends AbstractCanalClientTest {
    @Override
    public void processEntry(CanalEntry.Entry entry) {
        String tableName = entry.getHeader().getTableName();
        CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());

        for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
            if (tableName.equals("users")) {
                String userId = getColumnValue(rowData, "id");
                // 删除缓存
                redis.del("user:" + userId);
            }
        }
    }
}

优点:
- 解耦,应用代码无需关心缓存
- 基于数据库的最终状态,不会漏删
- 支持重试,可靠性高

缺点:
- 架构复杂
- 存在主从复制延迟 (通常<1秒)

方案3: 设置合理的过期时间 (兜底方案)

def update_user(user_id, new_name):
    # 1. 删除缓存
    try:
        cache.delete(f"user:{user_id}")
    except:
        pass  # 即使删除失败,也能通过过期时间兜底

    # 2. 更新数据库
    db.execute("UPDATE users SET name = ? WHERE id = ?", new_name, user_id)

# 读取时设置过期时间
def get_user(user_id):
    user = cache.get(f"user:{user_id}")
    if not user:
        user = db.query("SELECT * FROM users WHERE id = ?", user_id)
        cache.set(f"user:{user_id}", user, ttl=60)  # 60秒过期
    return user

优点:
- 即使缓存删除失败,最多60秒后自动恢复一致
- 简单可靠

缺点:
- 存在最多60秒的不一致窗口
- 需根据业务容忍度设置过期时间

方案4: 分布式锁保证原子性

import redis

def update_user(user_id, new_name):
    lock_key = f"lock:user:{user_id}"

    # 1. 获取分布式锁
    lock = redis_client.set(lock_key, 1, nx=True, ex=10)
    if not lock:
        raise Exception("获取锁失败,请重试")

    try:
        # 2. 删除缓存
        cache.delete(f"user:{user_id}")

        # 3. 更新数据库
        db.execute("UPDATE users SET name = ? WHERE id = ?", new_name, user_id)
    finally:
        # 4. 释放锁
        redis_client.delete(lock_key)

优点:
- 保证更新操作的互斥性
- 避免并发更新导致的数据不一致

缺点:
- 性能下降 (串行化)
- 锁超时时间不好设置

方案5: 强一致性方案 (不推荐)

def update_user(user_id, new_name):
    # 1. 开启分布式事务
    with distributed_transaction():
        # 2. 更新数据库
        db.execute("UPDATE users SET name = ? WHERE id = ?", new_name, user_id)

        # 3. 更新缓存
        cache.set(f"user:{user_id}", {"name": new_name}, ttl=3600)

    # 两者要么都成功,要么都回滚

问题:
- Redis不支持事务回滚
- 需要自己实现补偿逻辑
- 性能极差,不推荐

结论: 缓存本质是牺牲一致性换取性能,追求强一致性没有意义

不同业务场景的选择:

业务场景一致性要求推荐方案说明
用户信息Cache-Aside + 短TTL允许短暂不一致
商品信息Binlog异步删除1秒内最终一致
库存余额不使用缓存/分布式锁直接查数据库
文章阅读数极低Write-Behind异步批量更新
订单状态Cache-Aside + 延迟双删状态机保证

核心原则:

  1. 能接受最终一致性,就不要追求强一致性
  2. 更新数据库后删除缓存,而不是更新缓存
  3. 设置合理的过期时间作为兜底
  4. 高一致性要求的数据不使用缓存

二、消息队列

2.1 消息队列基础

Q4: 什么是消息队列?为什么需要消息队列?有哪些应用场景?

答案:

消息队列 (Message Queue, MQ) 的定义:

消息队列是一种异步通信机制,发送方(生产者)将消息发送到队列,接收方(消费者)从队列获取消息并处理。生产者和消费者解耦,互不依赖。

生产者 → [消息队列] → 消费者

特点:
- 异步: 生产者发送后立即返回,不等待消费者处理
- 解耦: 生产者和消费者不直接交互
- 削峰: 消费者按自己的速度处理,避免过载
- 可靠: 消息持久化,保证不丢失

为什么需要消息队列?

场景1: 异步处理

场景: 用户注册流程

同步方案 (无MQ):
def register(username, password, email):
    # 1. 写入数据库 (50ms)
    db.execute("INSERT INTO users ...")

    # 2. 发送邮件 (3000ms)
    send_email(email, "欢迎注册")

    # 3. 发送短信 (2000ms)
    send_sms(phone, "验证码:1234")

    # 4. 初始化用户数据 (500ms)
    init_user_data(user_id)

    return "注册成功"

总耗时: 50 + 3000 + 2000 + 500 = 5550ms
用户体验: 等待5.5秒才看到"注册成功",太慢!

异步方案 (使用MQ):
def register(username, password, email):
    # 1. 写入数据库 (50ms)
    user_id = db.execute("INSERT INTO users ...")

    # 2. 发送消息到MQ (1ms)
    mq.send("user.register", {
        "user_id": user_id,
        "email": email,
        "phone": phone
    })

    return "注册成功"

# 异步消费者
def handle_user_register(message):
    # 3. 发送邮件 (3000ms)
    send_email(message["email"], "欢迎注册")

    # 4. 发送短信 (2000ms)
    send_sms(message["phone"], "验证码:1234")

    # 5. 初始化用户数据 (500ms)
    init_user_data(message["user_id"])

总耗时: 50 + 1 = 51ms
用户体验: 立即看到"注册成功",体验极佳!
邮件和短信在后台异步发送,用户无感知

场景2: 流量削峰

场景: 电商秒杀活动

问题:
- 平时订单量: 100/秒
- 秒杀时订单量: 10万/秒
- 数据库最大承载: 1000/秒
- 直接请求数据库 → 崩溃!

同步方案 (无MQ):
def create_order(product_id, user_id):
    # 直接写数据库
    db.execute("INSERT INTO orders ...")  # 数据库被打爆!

异步方案 (使用MQ):
def create_order(product_id, user_id):
    # 1. 发送到消息队列
    mq.send("order.create", {
        "product_id": product_id,
        "user_id": user_id
    })

    return "订单提交成功,请稍后查看"

# 消费者按数据库承载能力消费
@rate_limit(1000)  # 每秒最多处理1000条
def handle_create_order(message):
    db.execute("INSERT INTO orders ...")

效果:
- 10万请求瞬间进入队列
- 消费者按每秒1000条的速度慢慢处理
- 100秒处理完,数据库压力平稳
- 用户看到"提交成功",体验良好

场景3: 系统解耦

场景: 订单系统与其他系统的交互

同步方案 (强耦合):
def create_order(order_data):
    # 1. 写订单表
    order_id = db.execute("INSERT INTO orders ...")

    # 2. 调用库存系统
    inventory_service.reduce_stock(order_data["product_id"], order_data["quantity"])

    # 3. 调用积分系统
    point_service.add_points(order_data["user_id"], order_data["amount"] * 0.01)

    # 4. 调用物流系统
    logistics_service.create_delivery(order_id)

    # 5. 调用推荐系统
    recommend_service.update_user_profile(order_data["user_id"], order_data["product_id"])

    return order_id

问题:
1. 订单系统依赖5个系统,任何一个挂了,下单失败
2. 新增营销系统需要发优惠券,需要修改订单系统代码
3. 积分系统升级,订单系统需要配合测试
4. 耦合严重,维护成本高

异步方案 (解耦):
def create_order(order_data):
    # 1. 写订单表
    order_id = db.execute("INSERT INTO orders ...")

    # 2. 发布订单创建事件
    mq.publish("order.created", {
        "order_id": order_id,
        "user_id": order_data["user_id"],
        "product_id": order_data["product_id"],
        "amount": order_data["amount"]
    })

    return order_id

# 各系统独立订阅事件
# 库存系统
@subscribe("order.created")
def reduce_stock(message):
    inventory_service.reduce_stock(message["product_id"], ...)

# 积分系统
@subscribe("order.created")
def add_points(message):
    point_service.add_points(message["user_id"], ...)

# 物流系统
@subscribe("order.created")
def create_delivery(message):
    logistics_service.create_delivery(message["order_id"])

# 新增营销系统,无需修改订单代码
@subscribe("order.created")
def send_coupon(message):
    coupon_service.send(message["user_id"], ...)

优点:
1. 订单系统只关心订单,不关心下游
2. 下游系统挂了,不影响下单
3. 新增系统,只需订阅事件,无需修改订单代码
4. 各系统独立部署,独立升级

场景4: 分布式事务

场景: 跨服务的数据一致性

问题:
- 下单需要: 扣库存 + 扣余额 + 创建订单
- 三个操作在不同的服务,如何保证原子性?

同步方案 (2PC两阶段提交):
coordinator.begin_transaction()
try:
    inventory_service.lock_stock(product_id, quantity)  # 阶段1: 准备
    account_service.lock_balance(user_id, amount)
    order_service.lock_order(order_data)

    inventory_service.commit()  # 阶段2: 提交
    account_service.commit()
    order_service.commit()
except:
    inventory_service.rollback()  # 回滚
    account_service.rollback()
    order_service.rollback()

问题:
- 阻塞: 阶段1锁资源,阻塞其他请求
- 单点: 协调者挂了,所有事务阻塞
- 性能差: 网络往返多,耗时长

异步方案 (Saga模式 + MQ):
def create_order(order_data):
    # 1. 发布订单创建事件
    mq.publish("order.saga.start", order_data)

# Saga编排
@subscribe("order.saga.start")
def handle_saga(order_data):
    # 步骤1: 扣库存
    result1 = inventory_service.reduce_stock(...)
    if not result1:
        mq.publish("order.saga.fail", {"step": "inventory"})
        return

    # 步骤2: 扣余额
    result2 = account_service.reduce_balance(...)
    if not result2:
        # 补偿: 恢复库存
        inventory_service.add_stock(...)
        mq.publish("order.saga.fail", {"step": "account"})
        return

    # 步骤3: 创建订单
    result3 = order_service.create(...)
    if not result3:
        # 补偿: 恢复库存和余额
        inventory_service.add_stock(...)
        account_service.add_balance(...)
        mq.publish("order.saga.fail", {"step": "order"})
        return

    mq.publish("order.saga.success", order_data)

优点:
- 非阻塞: 每步完成后释放资源
- 最终一致性: 通过补偿保证
- 高性能: 异步处理

缺点:
- 实现复杂: 需要编写补偿逻辑
- 中间状态可见: 可能出现库存扣了但订单未创建的状态

消息队列的核心优势总结:

优势说明典型场景
异步处理提升响应速度,改善用户体验用户注册、邮件发送
流量削峰保护后端系统,避免过载秒杀、促销活动
系统解耦降低系统间依赖,提高可维护性订单系统、事件驱动
可靠性消息持久化,保证不丢失支付通知、日志收集
扩展性水平扩展消费者,提高吞吐量大数据处理

常见的消息队列产品:

产品特点适用场景
RabbitMQ功能完善,支持多种协议传统企业应用
Kafka高吞吐,分布式,持久化日志收集,大数据
RocketMQ高可靠,事务消息电商,金融
ActiveMQ老牌MQ,JMS标准传统Java应用
Redis Streams轻量级,基于内存小规模异步任务

(未完待续...本文档包含缓存和消息队列基础部分,后续还有消息队列高级特性、可靠性保证等内容)