外观
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:13. 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命令遍历清理
