Skip to content

MySQL Redis缓存策略

缓存架构设计

缓存层级划分

  • 本地缓存:应用服务器本地缓存,如Ehcache、Caffeine,适合热点数据和只读数据
  • 分布式缓存:Redis集群,适合跨服务器共享数据和需要持久化的缓存
  • MySQL查询缓存:MySQL内置查询缓存,在高并发场景下性能不佳,MySQL 8.0已移除

缓存拓扑结构

  • 单节点Redis:适用于开发环境和小型应用
  • 主从复制Redis:提供高可用性,主节点写,从节点读
  • Redis Cluster:提供分布式存储和自动分片,适合大规模应用
  • Redis Sentinel:实现Redis高可用,自动故障转移

缓存策略设计

缓存数据分类

  • 静态数据:配置信息、字典表数据,缓存有效期长
  • 准静态数据:用户信息、商品基本信息,缓存有效期适中
  • 动态数据:订单数据、实时统计数据,缓存有效期短或实时更新

缓存更新策略

1. 主动更新(Write Through)

txt
# 主动更新流程
1. 应用先更新MySQL数据库
2. 成功后立即更新Redis缓存
3. 返回操作结果

# 优点:缓存与数据库强一致
# 缺点:写操作延迟增加,可能导致频繁更新缓存

2. 延迟更新(Write Behind)

txt
# 延迟更新流程
1. 应用先更新Redis缓存
2. Redis异步批量更新到MySQL
3. 返回操作结果

# 优点:写操作性能高,适合高并发写入场景
# 缺点:存在数据丢失风险,需要额外的持久化机制

3. 失效更新(Cache Aside)

txt
# 失效更新流程
1. 读取数据时,先查询Redis
2. 如果Redis存在,直接返回
3. 如果Redis不存在,查询MySQL
4. 将MySQL结果写入Redis,设置过期时间
5. 返回结果

# 写操作时:
1. 更新MySQL数据库
2. 删除Redis缓存

# 优点:实现简单,适合大多数场景
# 缺点:存在短暂的数据不一致窗口

缓存失效机制

1. 时间过期失效

txt
# 设置缓存时指定过期时间
SET key value EX 3600  # 1小时后过期

# 优点:实现简单,自动清理过期数据
# 缺点:可能导致缓存雪崩,需要使用随机过期时间

2. 主动删除失效

txt
# 数据更新时主动删除缓存
DEL key

# 优点:保证数据一致性
# 缺点:增加写操作复杂度

3. 惰性删除失效

txt
# Redis定期检查并删除过期键
# 访问过期键时返回nil,同时删除该键

# 优点:减少CPU消耗
# 缺点:可能占用过多内存

缓存一致性保障

1. 双写一致性问题

  • 问题:更新MySQL和Redis顺序不当导致数据不一致
  • 解决方案:先更新MySQL,再删除Redis缓存;使用分布式锁确保操作原子性

2. 缓存穿透问题

  • 问题:查询不存在的数据,导致请求直接穿透到MySQL
  • 解决方案
    • 缓存空值,设置较短过期时间
    • 实现布隆过滤器,快速判断数据是否存在
    • 对请求参数进行校验和限流

3. 缓存雪崩问题

  • 问题:大量缓存同时过期,导致请求集中到MySQL
  • 解决方案
    • 设置随机过期时间,避免缓存同时失效
    • 分层缓存,不同层级设置不同过期时间
    • 预热缓存,提前加载热点数据
    • 实现缓存降级,在缓存失效时返回默认值

4. 缓存击穿问题

  • 问题:热点数据过期瞬间,大量请求穿透到MySQL
  • 解决方案
    • 使用互斥锁,只允许一个线程更新缓存
    • 热点数据设置永不过期,定期后台更新
    • 实现二级缓存,本地缓存+分布式缓存

Redis缓存与MySQL集成实践

1. 数据序列化方案

  • JSON序列化:可读性好,跨语言支持,适合复杂数据结构
  • MessagePack序列化:二进制格式,体积小,性能高
  • Protobuf序列化:结构化数据,压缩率高,适合大规模数据传输

2. 缓存键设计规范

txt
# 缓存键命名规范
{业务模块}:{数据类型}:{主键值}:{字段名}

# 示例
user:info:12345:basic
order:detail:67890:items
product:list:category:electronics:page:1

3. Redis事务与管道

txt
# 使用Redis管道提高批量操作性能
Pipeline pipeline = jedis.pipelined();
for (User user : users) {
    pipeline.set("user:info:" + user.getId(), JSON.toJSONString(user));
    pipeline.expire("user:info:" + user.getId(), 3600);
}
pipeline.sync();

# 使用Redis事务保证操作原子性
jedis.watch("user:info:" + userId);
Transaction tx = jedis.multi();
tx.set("user:info:" + userId, newInfo);
tx.expire("user:info:" + userId, 3600);
tx.exec();

缓存监控与运维

1. Redis监控指标

  • 内存使用率:控制在最大内存的70%-80%
  • 命中率:理想值>90%
  • 连接数:避免超过最大连接限制
  • 延迟:网络延迟和Redis处理延迟
  • 键数量:监控键的增长趋势

2. 缓存性能优化

  • 使用合适的数据结构:根据业务场景选择String、Hash、List、Set、Sorted Set
  • 减少网络往返:使用MGET、HMGET等批量操作,Pipeline管道
  • 压缩数据:对大体积数据进行压缩后存储
  • 合理设置过期时间:根据数据更新频率调整

3. 缓存故障处理

  • Redis故障降级:当Redis不可用时,直接访问MySQL
  • 缓存数据恢复:Redis重启后,通过后台任务重新加载热点数据
  • 限流保护:在Redis故障时,对MySQL请求进行限流

版本兼容性考虑

Redis版本差异

  • Redis 3.0+:支持集群模式
  • Redis 4.0+:支持模块系统和更好的内存管理
  • Redis 5.0+:支持Stream数据结构,适合消息队列场景
  • Redis 6.0+:支持多线程IO和ACL访问控制

MySQL版本差异

  • MySQL 5.7:支持JSON数据类型,适合存储半结构化数据
  • MySQL 8.0:移除查询缓存,增强JSON支持,添加窗口函数
  • MySQL 8.0+:支持CTE(通用表表达式),提高复杂查询性能

常见问题(FAQ)

Q1: 如何选择合适的缓存更新策略?

A1: 选择缓存更新策略需要考虑业务场景:

  • 读多写少场景:优先使用Cache Aside策略
  • 写多读少场景:考虑使用Write Through策略
  • 高并发写入场景:考虑使用Write Behind策略
  • 强一致性要求:使用Write Through或Cache Aside策略

Q2: 如何处理缓存与数据库的一致性问题?

A2: 确保缓存一致性的常用方法:

  • 先更新数据库,再删除缓存
  • 使用分布式锁保证操作原子性
  • 实现最终一致性,通过定时任务校验和修复
  • 对于关键业务,使用强一致性方案

Q3: 如何应对缓存雪崩?

A3: 预防缓存雪崩的措施:

  • 设置随机过期时间,避免缓存同时失效
  • 实现分层缓存,不同层级设置不同过期时间
  • 预热缓存,提前加载热点数据
  • 实现缓存降级,在缓存失效时返回默认值
  • 使用Redis Cluster或Sentinel提高缓存可用性

Q4: 如何监控缓存性能?

A4: 监控缓存性能的关键指标:

  • 缓存命中率:反映缓存效果
  • 缓存穿透率:反映无效请求比例
  • 缓存更新频率:反映缓存稳定性
  • Redis内存使用率:避免内存溢出
  • Redis连接数:避免连接耗尽

Q5: Redis集群与单机Redis如何选择?

A5: 选择依据:

  • 开发环境和小型应用:单节点Redis足够
  • 中型应用:Redis主从复制+Sentinel
  • 大型应用和高并发场景:Redis Cluster
  • 需要自动分片和高可用:Redis Cluster

Q6: 如何处理Redis故障?

A6: Redis故障处理方案:

  • 实现自动故障转移:使用Redis Sentinel或Cluster
  • 缓存降级:当Redis不可用时,直接访问数据库
  • 限流保护:对数据库请求进行限流,避免压垮数据库
  • 数据恢复:Redis重启后,通过后台任务重新加载热点数据

Q7: 如何设计缓存键?

A7: 缓存键设计原则:

  • 唯一性:确保不同业务数据有不同的键
  • 可读性:便于调试和监控
  • 简洁性:减少内存占用和网络传输
  • 可扩展性:支持业务扩展和数据结构变化
  • 使用冒号分隔不同层级,如{模块}:{类型}:

Q8: 如何优化Redis内存使用?

A8: Redis内存优化方法:

  • 设置最大内存限制:maxmemory配置
  • 选择合适的内存淘汰策略:如volatile-lru
  • 压缩数据:对大体积数据进行压缩
  • 合理设置过期时间:自动清理过期数据
  • 使用合适的数据结构:如Hash代替多个String
  • 定期清理无用数据:使用SCAN命令遍历清理