第01章:让 Claude Code 帮我用 Redis 做缓存,它靠谱吗?

第01章:让 Claude Code 帮我用 Redis 做缓存,它靠谱吗?

你让AI给你的应用加了Redis缓存。数据库查询减少了,接口快了不少。
但是——缓存加对了吗?它会不会在某天给你带来比慢查询更棘手的问题:脏数据?

这一章告诉你:AI设计缓存方案时会默认遗漏什么,以及怎么用更好的提示词让AI给出真正能在生产环境用的缓存设计。


ℹ️ 版本说明:本章基于 Redis 8.0(2024年正式发布)。示例代码在 Python 3.13 + redis-py 5.x / Node.js 22 + ioredis 5.x 下测试通过。


1.1 AI默认会生成什么

你让AI给一个"获取用户信息"的接口加缓存:

def get_user(user_id: int) -> dict:
    # AI 生成的缓存代码
    cached = redis_client.get(f"user:{user_id}")
    if cached:
        return json.loads(cached)
    
    user = db.query(User).filter(User.id == user_id).first()
    if user:
        redis_client.set(f"user:{user_id}", json.dumps(user.to_dict()))
    return user.to_dict()

这段代码能跑,但有几个经典的生产问题没有处理。


1.2 AI通常遗漏的4个坑

⚠️ 坑1:缓存没有过期时间(内存会被填满)

上面的代码里,redis_client.set(...) 没有设置 ex= 参数(过期时间)。
这意味着一旦数据被缓存,它会永久存在于 Redis 内存中——直到内存被耗尽。

后果:Redis 内存随着时间线性增长,最终 OOM,缓存服务崩溃,所有请求直打数据库(缓存雪崩)。

⚠️ 坑2:没有处理缓存穿透(大量查询不存在的数据)

当用户查询一个不存在的 user_id 时,缓存里没有,数据库里也没有,代码直接返回 None——而且每次都不会被缓存。
攻击者可以用大量不存在的 ID 发请求,绕过缓存,直接把数据库打挂。

后果:恶意流量或爬虫能轻松绕过缓存,数据库直接被打爆。

⚠️ 坑3:序列化的数据类型不一致

AI通常用 json.dumps() 序列化,但如果你的数据包含 datetimeDecimalUUID 等类型,json.dumps() 会报错或序列化出错误的格式。

后果:线上出现 TypeError: Object of type datetime is not JSON serializable,缓存失败。

⚠️ 坑4:热点 key 同时过期(缓存击穿)

如果大量用户同时请求同一个热点数据(如首页排行榜),而这个数据的缓存恰好在这时过期——所有请求会同时穿透到数据库,数据库瞬间被大量重复查询打挂。


1.3 更好的提示词

提示词 P01:生成完整的 Cache-Aside 缓存实现

使用时机:为任何"查询单个实体"的接口加缓存(用户信息、商品详情、文章内容等)。

你是一个 Redis 8.0 + Python 3.13 缓存架构专家。

我需要给以下接口添加 Redis 缓存,使用 Cache-Aside 模式:
接口功能:[如:获取用户信息 / 获取商品详情]
数据特点:[如:更新频率低,1天约更新1次;每天约被请求10万次]

请生成完整的缓存实现,要求:
1. 设置合理的过期时间(说明为什么选这个时间)
2. 处理「缓存穿透」:对不存在的数据也缓存(缓存空值),有效期设为较短的时间
3. 处理「缓存击穿」:对热点 key 用 mutex 锁(只允许一个请求去数据库)
4. 序列化:处理 datetime、Decimal、UUID 等类型
5. 更新数据时:先更新数据库,再删除缓存(而不是更新缓存)
6. 缓存 key 命名规范:{业务}:{实体}:{id}

同时给出:
- 什么时候应该缓存这个数据(适合缓存的特征)
- 什么时候不应该缓存(实时性要求很高的数据)

技术栈:Python 3.13,redis-py 5.x,Redis 8.0

AI给出的改进版本(核心部分)

import json
import time
from datetime import datetime, date
from decimal import Decimal
from uuid import UUID
from typing import Optional
import redis

redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)

class CustomJSONEncoder(json.JSONEncoder):
    """处理 datetime、Decimal、UUID 等特殊类型"""
    def default(self, obj):
        if isinstance(obj, (datetime, date)):
            return obj.isoformat()
        if isinstance(obj, Decimal):
            return str(obj)
        if isinstance(obj, UUID):
            return str(obj)
        return super().default(obj)

CACHE_TTL = 3600        # 正常数据缓存1小时
NULL_CACHE_TTL = 60     # 空值缓存60秒(防止缓存穿透)
LOCK_TTL = 10           # 互斥锁10秒(防止缓存击穿)

def get_user(user_id: int) -> Optional[dict]:
    cache_key = f"user:info:{user_id}"
    
    # 1. 先查缓存
    cached = redis_client.get(cache_key)
    if cached is not None:
        if cached == "NULL":
            return None         # 缓存空值(穿透防护)
        return json.loads(cached)
    
    # 2. 缓存未命中,用分布式锁防止击穿
    lock_key = f"lock:user:{user_id}"
    lock_acquired = redis_client.set(lock_key, "1", ex=LOCK_TTL, nx=True)
    
    if not lock_acquired:
        # 其他请求正在查数据库,短暂等待后重试
        time.sleep(0.1)
        return get_user(user_id)
    
    try:
        user = db.query(User).filter(User.id == user_id).first()
        if user is None:
            # 缓存空值,防止穿透
            redis_client.set(cache_key, "NULL", ex=NULL_CACHE_TTL)
            return None
        
        user_dict = user.to_dict()
        redis_client.set(
            cache_key,
            json.dumps(user_dict, cls=CustomJSONEncoder),
            ex=CACHE_TTL
        )
        return user_dict
    finally:
        redis_client.delete(lock_key)   # 释放锁

def update_user(user_id: int, data: dict) -> dict:
    """更新用户:先更新DB,再删缓存(不是更新缓存)"""
    user = db.query(User).filter(User.id == user_id).first()
    for k, v in data.items():
        setattr(user, k, v)
    db.commit()
    
    # 删除缓存(让下次请求重新从DB加载)
    redis_client.delete(f"user:info:{user_id}")
    return user.to_dict()

提示词 P02:让 AI 审查现有缓存代码

使用时机:你已有AI生成的缓存代码,上线前做全面检查。

你是一个 Redis 8.0 缓存架构审查专家。
请对以下缓存实现做生产就绪度审查。

[粘贴缓存相关代码]

请按以下维度逐一检查,每个问题给出「严重程度:高/中/低」:

1. 所有缓存操作是否有过期时间(TTL)?
2. 是否有缓存穿透风险(不存在的数据反复查库)?
3. 是否有缓存雪崩风险(大量 key 同时过期)?
4. 是否有缓存击穿风险(热点 key 过期时的并发穿透)?
5. 序列化方式是否能处理所有数据类型?
6. 更新逻辑:是"更新DB后删除缓存"还是"更新缓存"?后者在并发下不安全。
7. 缓存 key 命名是否有命名空间(防止不同业务 key 冲突)?

输出:按严重程度排序,高优先级问题给出修复代码。

提示词 P03:为特定业务场景设计缓存策略

使用时机:你有一个新功能需要设计缓存,不确定用什么策略。

你是一个 Redis 8.0 缓存策略专家。

我需要为以下业务场景设计缓存策略,请帮我评估并选择最合适的方案:

业务场景:[描述场景,如:电商首页的商品推荐列表;用户的权限列表;文章的浏览计数]

数据特征:
- 读取频率:[如:每秒约 1000 次]
- 更新频率:[如:每小时更新一次 / 用户操作时实时更新]
- 数据大小:[如:单条约 500 字节,列表约 50 条]
- 实时性要求:[如:允许最多 60 秒的数据延迟 / 必须实时准确]

请评估以下策略哪个最合适:
1. Cache-Aside(旁路缓存):应用手动管理缓存,适合大多数场景
2. Write-Through(写穿透):写DB时同步写缓存,实时性好但写入慢
3. 定时刷新:定时任务更新缓存,适合不需要实时的聚合数据
4. 不缓存:实时性要求极高或数据太小,缓存收益不如复杂度代价

给出推荐方案、TTL建议、以及这个场景的缓存 key 设计规范。

1.4 缓存设计验收清单

检查项 验证方法 AI辅助
所有 SET 操作有 TTL 搜索代码:redis.set( 后面是否有 ex= 贴代码,用P02提示词检查
空值也被缓存(穿透防护) 测试:查询不存在的ID,观察Redis是否有 NULL 缓存 贴代码,问"不存在的数据会不会绕过缓存"
热点key有击穿防护 检查代码:是否有分布式锁或 singleflight 贴代码,问"如果100个请求同时访问同一个过期key会怎样"
序列化能处理特殊类型 测试:包含datetime/Decimal/UUID的数据能否正确序列化 贴数据结构,问"这里有没有序列化风险"
更新逻辑是"删缓存"而非"更缓存" 检查所有update/delete函数 贴代码,问"这里更新后缓存一致性是否有保证"

1.5 本章小结

如果你只记一件事
任何 redis.set() 调用都必须加 ex=(过期时间)。没有 TTL 的缓存是定时炸弹——内存会慢慢涨满,直到 Redis OOM 崩溃,然后所有流量同时打到数据库。

核心四步:P01生成P02审查按清单验收监控缓存命中率


→ 第02章:AI写的缓存代码,数据一致性有保障吗?