文章目录
- 1. 问题介绍
- 2. 解决方案
- 2.1 方案一:随机过期时间
- 2.2 方案二:增强 Redis 集群的可用性
- 2.3 方案三:多级缓存
- 2.3.1 做法
- 2.3.2 流程
- 2.3.3 示例代码
- 2.3.4 评价
- 2.4 方案四:限流
- 3. 总结
1. 问题介绍
缓存雪崩:大量缓存同时过期,或 Redis 宕机,导致大量请求访问 MySQL 数据库,导致其承受不住压力而宕机。
2. 解决方案
从问题介绍中可以发现,对于大量缓存同时过期的情况,只要让缓存的过期时间不同即可;对于 Redis 宕机的情况,则可以通过 增强 Redis (集群) 的可用性、多级缓存、限流 等方案解决。
2.1 方案一:随机过期时间
在缓存数据时,给固定的过期时间加上一个随机值,例如对于原本 10min 过期的数据:
java">redisTemplate.opsForValue().set("key", "value", 10, TimeUnit.MINUTES);
通过 new Random().nextInt(10)
(获取随机值还可以使用别的方法,并不局限于此) 给它加上一个 60s 以内随机值,从而避免大量数据同时在 10min 这一时刻过期,而是将其分散到 10min 后的 1min 时间段内过期。
java">redisTemplate.opsForValue().set("key", "value", 600 + new Random().nextInt(60), TimeUnit.SECONDS);
2.2 方案二:增强 Redis 集群的可用性
可以通过 Redis 的集群模式或哨兵模式来达到 Redis 的高可用 (之后会讲)。
2.3 方案三:多级缓存
2.3.1 做法
除了使用 Redis 这样的分布式缓存中间件之外,还可以使用 Caffeine、Guava 这样的框架实现应用内部的本地缓存,这样可以减轻 MySQL 的访问压力。
2.3.2 流程
2.3.3 示例代码
java">public Order get(long orderId) {// 先从 Caffeine 缓存中获取Order order = caffeineCache.getIfPresent(orderId);if (order == null) {String key = "order:" + orderId;// 如果 Caffeine 缓存未命中,再从 Redis 缓存中获取order = (Order) redisTemplate.opsForValue().get(key);if (order == null) {// 如果 Redis 缓存未命中,从 MySQL 中获取order = orderMapper.getById(orderId);if (order != null) {// 将数据放入 Redis 缓存redisTemplate.opsForValue().set(key, order, 3, TimeUnit.MINUTES);}}if (order != null) {// 将数据放入 Caffeine 缓存caffeineCache.put(orderId, order);}}return order;
}
2.3.4 评价
一般情况下,使用本地缓存的响应时间会少一点,因为避免了分布式缓存的网络 IO 时间。但这种方案实现起来比较复杂 (查询时需要查询两级缓存,更新时也一样),而且由于缓存在本地,还对服务器的内存有一定的要求。
2.4 方案四:限流
限流也可以避免缓存雪崩的问题,通过限制一个接口的访问量,从而防止大量请求通过,压垮 MySQL。
3. 总结
缓存雪崩指的是由于大量缓存同时过期或 Redis 宕机,导致大量请求直接访问 MySQL,以致其宕机的问题。
缓存雪崩还是很好理解的,不同问题的应对方案如下: