Dev Guide / Java开发技术guide / 中间件
content/dev-guide/a-Java开发技术guide/04-中间件/2-缓存.md
- 缓存:主要目的是加速访问(基于内存,响应快)、保护数据库资源(数据库操作成本高: SQL解析/锁/事务、资源有限)
- 经典读取模式:查询请求 -> 先查询缓存 -> 命中(直接返回) -> 未命中 -> 查数据库 -> 写入缓存 -> 返回
- 缓存读写策略:
- 旁路缓存(cache_aside,最常用):读(读缓存:命中直接返回 -> 未命中:读数据库 -> 写回缓存)、写(写数据库 -> 删除缓存:不是更新缓存)
- 读穿透(缓存作为代理,调用方不感知数据库):调用方 -> 缓存 -> 缓存未命中 -> 自动查数据库 -> 写入缓存 -> 返回)
- 写穿透(写操作同时写缓存和数据库。强一致性,但写入性能差,实际很少用):写请求 -> 写数据库 -> 写缓存 -> 返回
- 异步写回(性能最高,但风险最大):写请求 -> 写缓存 -> 返回(异步更新数据库)
- 三大经典问题与解决方案:
- 缓存击穿(热点key过期):互斥锁/SETNX、热点数据永不过期
- 缓存穿透(查询数据库不存在的数据,缓存无法命中):布隆过滤器、空值缓存
- 缓存雪崩(大量key过期/redis宕机):过期时间+随机值、redis集群、本地缓存兜底
- 多级缓存架构:
- 二级缓存(本地 + 分布式缓存):请求 -> Nginx(Lua 缓存,属于L1,本地缓存)→ 应用内存(Local Cache)→ Redis(属于L2分布式缓存) → MySQL
- 本地缓存:Guava Cache / Caffeine
- 缓存与数据库的一致性:
- 为什么很难保证强一致性:线程A更新数据库 -> 线程B读缓存(返回旧值) -> 线程A删缓存 -> 线程B查询数据库,写入缓存。T2和T3时间内,线程B返回了脏数据
- 最终一致性方案:延迟双删(删缓存 -> 更新数据库 -> 延迟再删:等读请求把旧数据写回缓存。业界最常用)、订阅 Binlog(更解耦)
- redis 持久化与恢复: RDB、AOF、混合持久化(RDB快速重写,AOF增量)
- 缓存监控与优化:
- 监控关键指标:内存使用率(健康值:<70%)、缓存命中率(如果缓存命中率过低,多一次网络开销,健康值:>80%)、慢查询、连接数、AOF大小
- 缓存大key问题(String类型超过10MB,Set超过5万元素。解决方案:拆分大Key):读取慢、过期删除时阻塞(redis单线程)。