目前市场上许多软件产品,其内部都采用 Redis 作为数据缓存的主要解决方案。随着业务的不断发展,在高并发场景里,Redis 常常会出现一些问题,网络上也有很多文章对其做出了总结。这里主要针对 缓存穿透、缓存雪崩、缓存击穿 这几类场景进行分析,并提出可能的处理方案。需要强调的是,以下的分析仅供参考,具体决策和实现效果必须要结合具体的业务场景,实事求是地进行应用
缓存穿透
缓存穿透指的是客户端向服务端请求 Redis 缓存和数据库中不存在的数据,导致所有的请求都穿透缓存处理阶段,直接打到数据库上。如果短时间内请求非常很多,数据库就会承受极大的压力。
发生 Redis 缓存穿透的场景可能会有以下几种:
- 热点数据采用懒加载。用户正常发起请求,但由于缓存中没有数据,而数据库中有数据,导致短时间内大量请求同时访问数据库,造成数据库压力过大。
- 数据库中有数据,且数据已经被缓存起来。用户正常发起请求,但恰好缓存中的数据过期了,从而导致大量请求同时访问数据库,造成数据库压力过大
- 用户恶意发起大量请求,查询缓存和数据库中都不存在的数据,导致每次请求都要到数据库中查询,造成数据库压力过大。
针对上述情况,有以下解决方法可供参考:
- 参数校验。
后端开发独立服务接口时,需要基于服务本身,对接口的请求入参做严格的校验和准入控制。对于不合理的请求参数,可适当 return null 或者空对象结束当前流程。对于后端来说,要有互不信任原则,毕竟无法确保用户或对接的业务方一定会严格按照接口设计传入安全的参数。 - 临时缓存。
对于数据不存在的请求参数,也将其 key 与结果短暂缓存起来,这样能避免大量相同请求瞬间打到数据库上,减轻压力。但这种方式也只是缓解请求压力而已,之后还是要从根本上分析解决问题。如果是存在恶意访问的用户特征,则可以在网关层限制这些用户的请求。 - 热加载缓存。
采用数据预热的方式。在可预见的大量请求到来之前,我们可以通过手动或者定时任务的方式,将相关的数据批量写入到 Redis 缓存中,这样就可以避免大量的基础数据请求直接打到数据库上,从而减轻数据库的读写压力,间接保护了数据库。 - 拦截请求。
可使用 Redis 自带的布隆过滤器,让它维护所有合法的 key,多次接收到不合理的请求时,则主动拦截请求。这种方案有一定的误差,但在大量的请求中,能有效地过滤掉绝大多数异常的请求。关于布隆过滤器的介绍,可以参考这篇文章:布隆过滤器(Bloom Filter)原理解析
对于临时缓存空数据以及布隆过滤器两种方案的对比,可以参考这篇文章:【Redis】Redis 高并发处理策略
缓存雪崩
缓存雪崩是指缓存数据的过期时间过于集中,在缓存同一时间大面积失效的情况下,大量请求涌入数据库,导致数据库无法承受而崩溃的现象。
可采用的解决方案如下:
- 给缓存过期时间加随机时间
在缓存过期时间基础上加上一个随机时间,使缓存过期时间散列开,避免缓存集中失效,有效防止缓存雪崩问题。但是,随机时间的设置需要合理,过长或过短都会对性能和缓存的使用效果产生负面影响。 - 加互斥锁
互斥锁可以保证同一时间只有一个请求可以访问数据库进行数据缓存,避免了大量请求同时访问数据库。但是,这种方法会导致系统的吞吐量明显下降,需要根据实际业务来决定是否使用。 - 热点数据不设置过期时间
热点数据不设置过期,请求可以一直获取到缓存的数据。但在这种情形下,用户请求不能确保获得的结果一定就是与数据库内容完全一致,这就涉及到一个数据一致性的问题。在某些业务场景下,还需要运维人员手动实现数据同步,保证缓存数据的正确性。
总而言之,缓存雪崩问题需要根据实际业务进行调整,不同业务的处理方式也不尽相同。在设置缓存过期时间时,需要合理设置随机时间,并根据业务需求来决定是否加入互斥锁。
缓存击穿
缓存击穿是指缓存的热点 key 过期或被删除后,导致线上原本能命中该热点 key 的请求,瞬间大量地打到数据库上,最终导致数据库被击垮。
可能导致缓存击穿的一些原因有:
- 热点 key 过期。缓存 key 过期,缓存数据查询失败,请求全都打到数据库上。
- 业务误操作。如果应用程序在处理数据时出现了误操作,例如错误地删除了缓存中的数据,就可能导致缓存击穿。
- 缓存设置不合理。如果应用程序缓存的数据量过大,或者缓存的数据类型与访问的数据类型不匹配,就可能导致缓存击穿。
- 黑客攻击:如果黑客攻击了应用程序的缓存,例如篡改了缓存数据,就可能导致缓存击穿。
应用程序开发人员需要根据实际情况进行排查,找出具体原因,及时进行解决。同时,应用程序开发人员还可以通过优化应用程序算法和缓存策略等方式来预防缓存击穿问题的发生。
可以采取以下防范措施防范缓存击穿问题:
- 及时维护缓存 key 和数据:缓存数据过期前及时更新缓存数据以及重置缓存 key 的过期时间,确保线上请求能够命中正确的 key 且拿到正确的数据。
- 加强权限管理:对线上请求的权限进行严格审核,避免误操作导致数据库崩溃。
- 优化数据结构:对数据结构进行优化,减少数据库被频繁访问的情况,提高缓存的命中率。
- 监控和预警:对线上请求和数据库操作进行实时监控和预警,及时发现问题并采取相应的措施,防止故障扩大化。
与缓存击穿相比,缓存雪崩指的是多个缓存失效导致的数据库接收请求过多的场景,而缓存击穿指的是单个缓存失效的场景。两者的处理思路可以互相参考。
Redis 服务可用性
缓存是 Redis 的一种,为了提高 Redis 的可用性,可以采取以下措施:
- 设置合理的过期时间。设置热点数据的过期时间,确保线上请求能够命中正确的 key。
- 加强权限管理。对线上请求的权限进行严格审核,特别是热点数据的访问权限,避免误操作导致数据库崩溃。
- 减少对缓存的依赖。对于热度非常大、访问频率非常高的数据,可以考虑在程序中加上本地缓存,比如 HashMap、List 等。
- 使用集群架构或主从 + 哨兵,提升保证 Redis 的可用性,如果主从挂了,可以自动将故障转移到备用主机上。
- 业务降级。从保护下游(接口或数据库)的角度考虑,针对大流量场景可以进行限流,这样即使缓存崩了,也不至于把下游服务给打挂。
- 降级开关和降级逻辑:提前写好降级开关和降级逻辑,关键时候执行降级方案,保证服务的正常进行。