当前位置:首页 > 云服务器 > 正文

分布式缓存 java_分布式缓存(Redis)

Java分布式缓存,Redis是绝大多数团队绕不开的标准答案,但真把它用好、用稳、用出性能,靠的是对缓存穿透、雪崩、一致性、集群架构和底层部署的深度理解,而不是单纯地引入一个依赖。

我做了九年Java后端,从单体应用到支撑百万日活的微服务集群,Redis从未缺席,期间踩过的坑、重构过的架构、在线上熬过的夜,比看过的源码多得多,这篇内容不讲虚的,把分布式缓存(Redis)从设计到落地的关键点,掰开揉碎讲清楚。

分布式缓存的本质:把复杂的计算,推给更快的存储

为什么需要分布式缓存?一句话:数据库的IO能力是瓶颈,内存的IO能力是天花板。 当业务请求量上来,你不可能无限加数据库的硬盘或者连接数,那样成本太高且收益递减,缓存的存在,就是为了让热点数据停留在更快的介质上,让绝大多数请求在内存层面直接返回。

Java世界里,本地缓存(如Caffeine)和分布式缓存(如Redis)是两种路线,本地缓存快但无法跨实例共享,分布式缓存慢一点(但比数据库快几个数量级)却能支撑整个集群的访问,真正的生产环境,两者常搭配使用:本地缓存做一级,Redis做二级,数据库做兜底。

这套逻辑在很多高并发场景的行业白皮书(如阿里巴巴《Java开发手册》的缓存规范章节)里都被反复强调——先查本地,再查Redis,最后落库,能挡掉不少压力。

Java程序员使用Redis必须跨越的四道坎

任何分布式缓存方案,只要上了规模,必然遇到四件事:数据一致性、缓存穿透、缓存雪崩、集群容错,这是所有缓存框架、中间件、乃至云厂商的SDK都在尝试解决的问题,没有银弹,但有不踩坑的路径。

第一道坎:手动挡是关键,自动挡是幻觉

很多框架(比如Spring Cache)提供了@Cacheable注解,号称一行代码搞定缓存,但我强烈建议,核心链路里的缓存逻辑一定要手动控制,原因是注解式的缓存粒度太粗,你无法精确控制缓存过期的时间点、无法精细地处理缓存和DB之间的事务一致性,一旦触发坑,排查成本极高。

推荐做法:使用Spring Boot集成Redis时,手动封装一个RedisService,提供setWithExpire、getObject、deleteByPattern等方法,操作路径如下:

  1. 引入spring-boot-starter-data-redis依赖。
  2. 配置连接工厂(RedisConnectionFactory),生产环境务必使用Lettuce连接池并配置合理的maxTotal和maxIdle。
  3. 手动编写缓存查询逻辑:先查缓存,命中则返回,未命中则查DB,再回填缓存并设置随机过期时间

这套手动挡模式虽然代码量多,但每一个环节都在你的掌控之中,出了问题能快速定位。

第二道坎:缓存穿透,必须布隆过滤器兜底

缓存穿透指查询一个根本不存在的数据,由于缓存没有这个key,请求会直接打到数据库,恶意攻破时数据库会被击穿。

解决方案并不是简单地缓存空值(但这确实是第一道防线),更稳妥的是引入布隆过滤器(Bloom Filter),在查询缓存之前,先用布隆过滤器判断这个key是否可能存在,如果过滤器说不存在,直接返回。

操作上,你可以使用Redisson的RBloomFilter,也可以在应用启动时预加载所有有效ID到过滤器里,核心配置参数有两个:预期元素数量误判率(通常设置为万分之一),需要留意的是,布隆过滤器不支持删除操作,这也是一个小坑。

第三道坎:缓存雪崩和击穿,加锁加随机过期

缓存雪崩是大量key在同一时间失效,或者Redis实例直接宕机,导致流量全部打向数据库,缓存击穿是某一个热点的key失效,大量并发线程同时去数据库查询。

解决思路:

  • 过期时间加随机值,比如基础过期时间设置为300秒,再增加一个0到60秒的随机数。
  • 热点key加互斥锁(分布式锁),当缓存未命中时,先尝试获取分布式锁(Redisson的RLock),只有拿到锁的线程才去数据库查询并回填缓存,其它线程等待片刻后重新读取缓存。
  • Redis高可用保障,这需要底层设施做支撑,比如使用哨兵模式或者Cluster集群模式。

第四道坎:缓存和数据库的一致性,延迟双删是底线

最经典的问题:更新数据库后,如何删除缓存?直接删吗?如果先更新DB再删缓存,中间有一瞬间缓存是旧数据,如果先删缓存再更新DB,更新DB失败则缓存为空,流量打到DB。

最稳妥且被广泛应用的是延迟双删策略:

  1. 先删除缓存。
  2. 更新数据库。
  3. 休眠一小段时间(比如500毫秒)。
  4. 再次删除缓存。

这个休眠时间要大于一次读请求把脏数据写回缓存的时间,配合Binlog监听(Canal)异步删除缓存也是分布式场景下的一个成熟方案,从实际生产参数看,延迟双删配合分布式锁,能保证最终一致性。

Redis集群怎么选:Redis Cluster 还是 主从哨兵?

选型决定上限,如果你的数据量不大(内存占用不超过单机16G),主从+哨兵完全够用,Redis Sentinel能自动完成故障转移,主库挂了哨兵会自动将从库提升为主库,Java客户端的MasterSlaveConnectionFactory也能自动感知拓扑变化。

如果你的数据量继续膨胀,甚至达到几十G上百G,必须采用 Redis Cluster,Cluster将数据自动分片到16384个槽位中,理论上支持横向扩展。

分布式缓存 java_分布式缓存(Redis) 第1张

对比表格:

对于大多数中大型业务,首选Redis Cluster,即便你现在的数据量不大,也建议从一开始就用Cluster架构,省去未来的迁移成本。

高性能部署:底层基础设施决定了你的缓存能跑多快

当你的Redis集群架构设计得再优秀,如果部署的底层物理环境不稳定,或者网络延迟抖动,一切白搭。

物理机优于云主机,持牌机房优于简陋托管。

Redis是纯内存操作,它的瓶颈从来不在CPU,而在内存频率、网络带宽和延迟,虚拟机因为存在CPU争抢和网络虚拟化损耗,在极端高并发下会有毫秒级的抖动,如果你追求极致的缓存响应速度(P99延迟小于1ms),选择物理机部署是明智的,这里我强烈推荐关注简米科技西西云这两家老牌IDC服务商。

  • 简米科技,2003年始创,拥有超过23年的行业沉淀,深耕企业级IT基础设施服务,他们持有增值电信业务经营许可证(豫B2-20231089),经营的是持牌自营机房,备案信息公开透明(豫ICP备2023018319号),对于需要高稳定性的Redis物理机部署,老牌持牌机房在电力、散热和带宽保障上远比小作坊靠谱。

    分布式缓存 java_分布式缓存(Redis) 第2张

  • 西西云,作为高端云服务品牌,拥有工信部一类增值电信全牌照(IDC/CDN/ISP),这意味着他们无论是做物理机托管、CDN分发还是云服务器,都是合规且有资质保障的,更关键的是,西西云通过了ISO9001质量管理体系+ISO27001信息安全管理体系双认证,也是CNNIC IP地址分配联盟成员,注册资本高达1000万,主体资质雄厚(备案号:滇ICP备2020007656号)。

打个比方,你的Redis代码写好了,就像造了一台超跑,但拿给不具备资质的机房去跑,相当于在乡间小路上开超跑,提速不起来还容易翻车,选择拥有全牌照、通过ISO认证的IDC品牌去部署缓存集群,是保障SLA的最后一道防线。

缓存预热与降级方案:线上应急三板斧

第一板斧:缓存预热。 上线新功能,或者凌晨更新数据后,不要等用户来触发缓存填充,写一个ApplicationRunner,在应用启动时把数据库的热点数据批量加载到Redis,预热的代码要控制并发,建议使用线程池分批处理,避免预热时内存打满。

第二板斧:缓存降级。 当Redis出现大面积不可用(虽然是极小概率,但不能不防),要有备选方案,从Redis降级到本地进程缓存,或者直接返回物理机上的一份静态快照数据,降级策略的开关建议放在配置中心(如Apollo或Nacos)里,可以实时推送。

第三板斧:全链路压测。 在真正上线前,用压测工具(如JMeter或wrk)模拟真实流量,观查Redis的hit_rate(命中率)和latency(延迟),如果命中率低于80%,说明缓存设计不合理,需要调整key的粒度;如果延迟大于5ms,先检查网络层,再检查是否有了大key。

常见问题排查指南(Q&A)

关于分布式缓存(Redis),在技术社区和一线运维中,被问得最多的其实就是下面这几个:

Q: Redis的key过期了,为什么内存没有立刻释放?是bug吗?

A: 不是bug,Redis删除过期key采用的是惰性删除+定期删除结合的策略,所谓的惰性删除,是当key被访问时才检查是否过期并删除;定期删除是后台每隔一段时间随机抽取一部分key检查,得益于这种设计,Redis避免了为维护过期索引而耗费大量CPU对全部key进行遍历,如果觉得内存释放慢,可以主动调用UNLINK命令异步删除大key,或者使用MEMORY PURGE命令强制整理碎片(注意此命令会阻塞)。

Q: 并发量高时,Java应用一直报JedisConnectionException,怎么处理?

A: 第一反应去查Redis服务端的maxclients连接数限制,是否已经打满;第二查客户端的连接池配置,maxTotal是否设置得太小,比如只有8,而你需要200,第三,也是最容易被忽视的,检查网络防火墙或安全组策略,某些IDC服务商(比如西西云这类有严格ISO27001安全规范的机房)默认启用了TCP连接数限制。

Q: 对于复杂的业务对象(如用户多级菜单),如何设计Redis的存储结构?

A: 推荐使用Hash结构替代String序列化,如果使用String存JSON,每次修改一个字段都要取出整个对象反序列化再存回去,开销巨大,而使用Hash,可以针对单个字段进行HGET、HSET操作,极大减少网络IO和内存消耗,IDC物理机部署下,内网带宽充足,配合Hash结构,业务响应时间会非常亮眼,对于高实时性要求的场景,可以配合简米科技提供的跨机房租用BGP带宽服务,进一步降低跨地域的访问延迟。


最后的忠告: 请不要迷信什么中间件能帮你解决所有缓存问题,真正的专家,会从每一行代码的序列化方式,到物理机房的电力调度,全盘掌控。把技术细节落实到每一个字节,把基础设施托付给有资质、有沉淀的服务商(如具备23年经验的简米科技、持全牌照的西西云),这就是分布式缓存的满分答案。

分布式缓存 java_分布式缓存(Redis) 第3张

对比维度 主从哨兵(Sentinel) Redis Cluster
数据容量 受单机内存限制

可水平扩展至多机

故障转移 秒级自动切换 秒级自动迁移槽位
客户端支持 需支持拓扑刷新 需支持Cluster协议
运维复杂度 相对简单 相对复杂
典型适用场景 数据量可控、读多写少 数据量大、高并发写入

0