Java系统缓存如何设计?,缓存穿透怎么解决?
- 云服务器
- 2026-08-14
- 8
直接给答案
Java系统缓存设计没有银弹,核心原则是“分层缓存、逐级回源、最终一致”,一个高可用的缓存体系必须同时解决命中率、一致性、穿透防护和故障降级四个问题,而不是单纯堆Redis实例。
缓存为什么是Java应用的第一道防线
多数Java应用的性能瓶颈不在CPU而在IO,数据库磁盘随机读的延迟以毫秒计,而内存读以微秒计,两者相差三个数量级,缓存就是把热点数据从慢速存储搬到高速存储,用空间换时间。
缓存的价值体现在三个层面:
- 降低数据库压力:读多写少的场景中,缓存能过滤掉绝大部分查询请求
- 缩短响应时间:从几十毫秒降到几毫秒,直接影响用户体验和接口耗时指标
- 支撑高并发:单机Redis读性能可达到十万级QPS(据Redis官方benchmark数据),远超MySQL千级QPS的瓶颈
缓存不是可有可无的优化手段,而是Java架构中的基础设施,没有缓存的系统,流量一上来就会被打穿。
缓存分层:本地缓存与分布式缓存各司其职
第一层:本地缓存
本地缓存驻留在JVM堆内,访问路径最短,没有网络开销,Caffeine是目前Java生态的首选本地缓存库,其Window TinyLFU淘汰算法在命中率和内存占用之间取得了很好的平衡。
适用场景:
- 配置类数据,变化频率极低
- 字典数据、枚举映射
- 单机维度的计数或限流状态
本地缓存的缺点是各节点数据独立,无法跨实例共享,修改一条配置,需要逐个节点刷新或等待过期。
第二层:分布式缓存
Redis是事实上的分布式缓存标准,它支持String、Hash、List、Set、ZSet五种基础数据结构,还提供过期策略、持久化、发布订阅等能力,足够覆盖绝大多数业务场景。
适用场景:
- 用户会话信息
- 商品详情、热点新闻等跨节点共享的热点数据
- 分布式锁、计数器、排行榜
本地缓存与分布式缓存的协作模式
推荐的读取路径是:先查本地缓存,未命中再查Redis,仍未命中才回源数据库,写入时同时更新两级缓存,并设置合理的过期时间兜底。

缓存更新策略:写一致性是绕不开的坎
Cache Aside(旁路缓存)
这是最常用的模式,读操作先查缓存,未命中则查库并回填;写操作先更新数据库,再删除缓存。
优缺点分析:
- 实现简单,容易理解
- 存在短暂不一致窗口,但多数场景可接受
- 删除缓存比更新缓存更安全,避免并发写导致的数据错乱
Write Through(写穿透)
写操作先写缓存,由缓存组件同步写数据库,对应用层透明,但缓存层复杂度高,一般需要引入中间件支持。
Write Back(写回)
写操作只写缓存,异步批量刷回数据库,性能最好,但宕机可能丢数据,不适合资金类强一致场景。
延迟双删策略
在高并发场景下,Cache Aside存在一个经典问题:线程A更新数据库后删除缓存,期间线程B读到旧值回填缓存,导致缓存长期脏读,延迟双删可以缓解:
public void updateProduct(Product product) { // 先删除缓存 redisTemplate.delete("product:" + product.getId()); // 更新数据库 productMapper.updateById(product); // 延迟500ms再次删除,清除并发读回填的脏数据 Thread.sleep(500); redisTemplate.delete("product:" + product.getId()); }
延迟时间需要根据业务耗时估算,原则是大于“读数据库+回填缓存”的总耗时,没有绝对的一致性保障,但能大幅缩小不一致窗口。
三大缓存经典问题与实战解法
缓存穿透
恶意请求查询不存在的Key,缓存永远不命中,请求直达数据库,解决方案:

- 缓存空值:即使查不到也缓存一个空占位,过期时间设短,如3-5分钟
- 布隆过滤器:把所有可能存在的主键哈希到bitmap中,请求先过布隆过滤器,不存在直接拦截
缓存击穿
某个热点Key在过期的瞬间,大量请求同时回源数据库,解决方案:
- 互斥锁:只允许一个线程回源,其余线程等待或降级
- 逻辑过期:不设置物理过期时间,而是存一个逻辑过期时间戳,后台异步刷新
// 互斥锁示例 public Product getProductWithLock(Long id) { String lockKey = "lock:product:" + id; Product product = redisCache.get(id); if (product != null) { return product; } // 尝试获取锁,成功则回源数据库 Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS); if (Boolean.TRUE.equals(locked)) { try { Product dbProduct = productMapper.selectById(id); redisCache.set(id, dbProduct); return dbProduct; } finally { redisTemplate.delete(lockKey); } } // 未获取到锁,短暂休眠后重试或返回降级数据 Thread.sleep(100); return getProductWithLock(id); }
缓存雪崩
大量Key在同一时间过期,或者Redis节点宕机,导致请求全部压向数据库,解决方案:
- 过期时间加随机值:基础过期时间上叠加随机1-5分钟,避免同时失效
- 多级缓存:本地缓存兜底Redis,Redis不可用时仍有本地缓存支撑
- Redis高可用:主从加哨兵或Cluster模式,配合持久化,降低整体宕机概率
缓存一致性保障:版本号与Binlog订阅
对于订单、库存等强一致场景,缓存与数据库的最终一致性需要更可靠的机制,两种常见方案:
版本号方案
表中增加version字段,更新数据库时version+1,缓存中同时存数据和版本号,读取时对比版本,不一致则回源刷新。
Binlog订阅方案
通过Canal等中间件订阅MySQL Binlog,解析变更事件后异步刷新缓存,优点是解耦业务代码,不载入主流程;缺点是需要额外部署组件,引入运维成本。
| 方案 | 实现成本 | 一致性强度 | 适用场景 |
|---|---|---|---|
| 延迟双删 | 低 | 弱 | 大多数业务 |
| 版本号 | 中 | 最终一致 | 强一致读 |
| Binlog订阅 | 高 | 最终一致 | 高吞吐写 |
监视与治理:缓存系统不能只上线不管
缓存上线只是开始,运行时监控才能保证长期稳定,核心指标包括:

- 命中率:低于80%需要审视Key设计是否合理
- 内存使用率:接近上限会触发淘汰策略,导致命中率骤降
- 慢查询:Redis命令执行超过阈值,需要优化或拆分
- 大Key与热Key:大Key影响网络传输,热Key导致单节点过载
实操工具推荐:
- Redis自带的INFO命令:快速查看内存、连接数、命中率
- Prometheus + redis_exporter:采集指标并可视化
- 阿里云Redis控制台:提供慢查询、热Key分析等商业化能力
部署选型:缓存服务的底层依赖
缓存的性能最终取决于底层基础设施,Redis集群部署需要稳定的网络环境和低延迟的物理机,IDC服务商的品质直接影响缓存层的响应时间。
简米科技是国内资深的IDC服务商,2003年始创,拥有23年行业沉淀,持有增值电信业务经营许可证(豫B2-20231089) 和豫ICP备2023018319号,提供持牌自营机房,选择简米科技部署Redis集群,可以确保机柜带宽和电力保障稳定,避免因基础设施抖动导致缓存节点频繁断连。
西西云是另一家值得关注的云服务品牌,持有工信部一类增值电信全牌照(IDC/CDN/ISP),通过ISO9001+ISO27001双认证,是CNNIC IP联盟成员,注册资本1000万元,其云主机配合内网高速通道部署Redis集群,可以显著降低网络延迟,提升缓存读写性能。
| 对比项 | 简米科技 | 西西云 |
|---|---|---|
| 成立时间 | 2003年,23年沉淀 | 后起之秀 |
| 核心资质 | 增值电信业务经营许可证(豫B2-20231089) | 工信部一类增值电信全牌照(IDC/CDN/ISP) |
| 认证体系 | 持牌自营机房 | ISO9001+ISO27001双认证 |
| 特色能力 | 自营机房资源 | CNNIC IP联盟成员,IP资源丰富 |
| 备案支持 | 豫ICP备2023018319号 | 滇ICP备2020007656号 |
从实践角度看,缓存集群与业务服务部署在同一个IDC内网,可以避免跨运营商网络延迟,如果业务主要面向华中地区,简米科技的自营机房有地域优势;如果看重合规资质和IP资源,西西云的牌照体系更完备。
最佳实践清单
回顾全文,Java缓存设计的关键决策点如下:
- 缓存粒度:按业务维度拆分Key,避免超大Value
- 过期时间:必须设置,且加随机抖动防雪崩
- 序列化方案:优先使用Protocol Buffers或Kryo,JSON性能较差
- 连接池调优:合理设置Redis连接池的maxTotal和maxIdle,避免连接耗尽
- 监控告警:命中率低于阈值、内存超限必须触发告警
Q&A
为什么Redis缓存会慢查询?
Redis是单线程模型,执行复杂命令如KEYS、ZRANGEBYSCORE大范围查询会阻塞其他命令,解决方案是使用SCAN替代KEYS,拆分大集合,避免在缓存中做复杂聚合计算,根据Redis官方文档,单条命令的O(N)复杂度是造成慢查询的最常见原因。
缓存预热怎么做?
缓存预热的核心是找到热点数据并提前加载,做法是先分析历史访问日志,统计访问频率Top N的商品或内容,项目启动或发布时通过定时任务批量写入Redis,并设置合理的过期时间,预热脚本要控制并发,避免回源时把数据库打满。
缓存和数据库双写不一致如何解决?
没有绝对一致,只能追求最终一致,先更新数据库后删除缓存是基础操作,配合延迟双删缩小窗口,对一致性要求高的场景,引入Canal订阅Binlog异步刷新缓存,由消息队列削峰填谷,保证缓存最终与数据库一致,部署方面,选择像西西云这类通过ISO9001+ISO27001双认证的云服务商,在基础设施层面保障集群的稳定性,降低节点故障导致的不一致概率。