Redis缓存 + 本地缓存实战指南

3 阅读6分钟

目录

  1. 为什么需要缓存?
  2. Redis 缓存详解
  3. 本地缓存选型指南
  4. Redis + 本地缓存组合策略
  5. 业务场景案例
  6. 常见问题与解决方案
  7. 最佳实践总结

一、为什么需要缓存?

1.1 数据库的痛点

  • :MySQL 单表超过千万级,查询可能就是几百毫秒甚至秒级
  • :数据库连接是稀缺资源,高并发时连接池分分钟耗尽
  • :大流量一来,数据库直接挂给你看

1.2 缓存的价值

指标无缓存有缓存
响应时间200-500ms5-20ms
QPS 能力几百几万+
DB 负载极低
成本

二、Redis 缓存详解

2.1 Redis 核心数据类型与使用场景

2.1.1 String - 最常用

场景:热点数据缓存、计数器、Session

import redis

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

# 1. 缓存用户信息
user_id = "10001"
cache_key = f"user:profile:{user_id}"
user_data = r.get(cache_key)
if not user_data:
    user_data = db.query(f"SELECT * FROM users WHERE id={user_id}")
    r.setex(cache_key, 3600, json.dumps(user_data))

# 2. 计数器
r.incr("api:count:users:10001")

# 3. 分布式锁 - 秒杀场景
lock_key = "lock:seckill:product:888"
is_locked = r.set(lock_key, "1", nx=True, ex=10)
if is_locked:
    try:
        r.decr("stock:product:888")
    finally:
        r.delete(lock_key)

2.1.2 Hash - 对象存储

场景:购物车、用户画像

# 购物车
cart_key = "cart:user:10001"
r.hincrby(cart_key, "product:888", 2)
cart_items = r.hgetall(cart_key)

2.1.3 List/Stream - 消息队列

场景:异步任务、延迟队列

# 延迟队列
r.lpush("delay:order:cancel", json.dumps({"order_id": "ORDER_001"}))

2.1.4 Sorted Set - 排行榜

场景:热搜榜、积分榜

r.zadd("hot:search", {"口罩": 1000, "iPhone15": 800})
r.zincrby("hot:search", 1, "口罩")
top10 = r.zrevrange("hot:search", 0, 9, withscores=True)

2.2 Redis 缓存模式

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

应用 → 查缓存 → 未命中 → 查DB → 写入缓存 → 返回

2.2.2 Write-Through(写穿透)

应用 → 写DB → 同步写缓存 → 返回

2.2.3 Write-Behind(异步写回)

应用 → 写缓存 → 异步批量写DB

2.3 Redis 集群与高可用

部署模式特点适用场景
主从复制一主多从,读写分离中小规模
Sentinel自动故障切换生产环境高可用
Cluster分片存储,水平扩展大规模数据

三、本地缓存选型指南

3.1 主流方案对比

方案语言性能适用场景
CaffeineJava⭐⭐⭐⭐⭐Java 服务首选
Guava CacheJava⭐⭐⭐⭐简单场景
EhcacheJava⭐⭐⭐需要持久化
node-cacheNode.js⭐⭐⭐⭐Node.js 项目
PySimpleCachePython⭐⭐⭐Python 项目

3.2 Caffeine 详解(Java 首选)

Cache<String, User> cache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(5, TimeUnit.MINUTES)
    .recordStats()
    .build();

cache.put("user:1001", new User("1001", "张三"));
User user = cache.getIfPresent("user:1001");

淘汰策略

Cache<String, Object> cache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .maximumWeight(100 * 1024 * 1024)
    .weigher((key, value) -> estimateSize(value))
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build();

统计与监控

CacheStats stats = cache.stats();
System.out.println("命中率: " + stats.hitRate());
System.out.println("驱逐数量: " + stats.evictionCount());

四、Redis + 本地缓存组合策略

4.1 经典架构

请求 → 本地缓存(Caffeine) → 未命中 → Redis → 未命中 → 数据库

4.2 读写策略

读策略:Cache-Aside + 本地缓存

def get_user(user_id):
    local_key = f"user:local:{user_id}"
    redis_key = f"user:redis:{user_id}"
    
    # 1. 先查本地缓存
    user = local_cache.get(local_key)
    if user:
        return user
    
    # 2. 查 Redis
    user = redis.get(redis_key)
    if user:
        local_cache.set(local_key, user, ttl=300)
        return user
    
    # 3. 查数据库
    user = db.query(f"SELECT * FROM users WHERE id={user_id}")
    if user:
        redis.setex(redis_key, 3600, json.dumps(user))
        local_cache.set(local_key, user, ttl=300)
    return user

写策略

def update_user(user_id, data):
    redis.delete(f"user:redis:{user_id}")
    local_cache.delete(f"user:local:{user_id}")
    db.update(f"UPDATE users SET ... WHERE id={user_id}")
    time.sleep(0.1)  # 延迟双删
    redis.delete(f"user:redis:{user_id}")

4.3 组合优势

优势说明
抗高峰本地缓存扛住 90% 突发流量
低延迟本地 0.1ms vs Redis 1-5ms
高可用Redis 挂了本地还能撑一阵
成本减少 Redis 请求次数

五、业务场景案例

5.1 案例一:电商商品详情页

背景:亿级商品库,QPS 峰值 10 万/秒

解决方案:三级缓存架构

请求 → Nginx缓存(1s) → 本地缓存(1min) → Redis(10min) → DB

效果

  • 本地缓存命中率 70%
  • Redis QPS 从 10 万降到 3 万
  • 响应时间从 200ms 降到 15ms

5.2 案例二:用户信息与权限缓存

痛点:每次请求都查 Redis,网络开销大

方案:用户信息本地缓存(5分钟) + 权限实时校验


5.3 案例三:分布式锁 + 本地锁防击穿

背景:热点商品秒杀,瞬时 QPS 10 万

方案:Redis 分布式锁 + 本地锁双重保护

local_lock = Lock()

def seckill(product_id, user_id):
    with local_lock:
        is_locked = redis.set(f"lock:seckill:{product_id}", user_id, nx=True, ex=10)
        if not is_locked:
            return seckill(product_id, user_id)
        
        stock = redis.decr(f"stock:{product_id}")
        if stock < 0:
            redis.incr(f"stock:{product_id}")
            return {"success": False, "msg": "已售罄"}
        
        save_order_async(user_id, product_id)
        return {"success": True}
    redis.delete(f"lock:seckill:{product_id}")

5.4 案例四:配置中心 + 本地缓存

方案:配置本地缓存 + 监听变更实时更新


5.5 案例五:实时排行榜

场景:直播间礼物榜、游戏战力榜

方案:Redis ZSet + 本地缓存兜底

r.zincrby(f"rank:gift:{room_id}", value, user_id)

def get_top10(room_id):
    local_key = f"local:rank:{room_id}"
    if local_key in local_cache:
        return local_cache[local_key]
    top10 = r.zrevrange(f"rank:gift:{room_id}", 0, 9, withscores=True)
    local_cache[local_key] = top10
    return top10

六、常见问题与解决方案

6.1 数据一致性

方案A:延迟双删

redis.delete(key)
db.update(...)
time.sleep(0.2)
redis.delete(key)

方案B:订阅binlog

  • 使用 Canal 监听 MySQL binlog

方案C: TTL 兜底

redis.setex(key, 300, value)  # 5分钟

6.2 缓存击穿

方案A: 互斥锁

lock = redis.set(lock_key, "1", nx=True, ex=10)
if not lock:
    time.sleep(0.05)
    return get_user(user_id)
# ...查DB写缓存

方案B:永不过期 + 异步更新


6.3 缓存雪崩

  1. 随机过期时间
  2. 预热缓存
  3. Redis 集群高可用

6.4 本地缓存与 Redis 不一致

def update_user(user_id, data):
    db.update(...)
    redis.setex(key, 3600, json.dumps(data))
    local_cache.delete(local_key)
    redis.publish("cache:clear", local_key)

6.5 内存膨胀

必须设置上限和淘汰策略:

Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .recordStats()
    .build();

6.6 多节点数据同步

方案复杂度
Redis 集中式缓存
发布/订阅通知
定时全量同步

七、最佳实践总结

7.1 缓存使用原则

  1. 读多写少 → 用缓存;写多读少 → 慎用
  2. 缓存非核心数据(不要缓存钱、库存)
  3. 设置 TTL
  4. 命中率 < 50% 就该优化

7.2 分层策略

数据类型缓存层TTL
配置、字典本地缓存小时级
用户信息本地 + Redis5-30分钟
商品详情本地 + Redis10-30分钟
实时数据只用 Redis1-10分钟

7.3 技术选型

本地缓存:Java → Caffeine | Python → dict | Node.js → node-cache
集中缓存:Redis(单实例/Cluster/Sentinel)
架构:读 → 本地 → Redis → DB | 写 → DB → Redis → 删除本地

7.4 监控指标

  • Redis:qps、内存使用率、命中率
  • Caffeine:hit_rate(>80%)、eviction_count、estimated_size

作者:Caro | 版本:v1.0