Redis 做缓存虽减轻了 DBMS 的压力,减小了 RT,但在高并发情况下也是可能会出现各 种问题的。
1 缓存穿透
当用户访问的数据既不在缓存也不在数据库中 时,就会导致每个用户查询都会"穿透" 缓存"直抵 "数据库。这种情况就称为缓存穿透 。一个两个请求无所谓,当高并发的访问 请求到达时,缓存穿透不 仅增加了响应时间 ,而且还会引发对 DBMS 的高并发查询,这种高并发查询很可能会导致DBMS 的崩溃。
缓存穿透产生的主要原因 有两个:一是在数据库中没有相应的查询结果 ,二是查询结果为空 时,不对查询结果进行缓存。所以,针对以上两点,解决方案也有两个:
对非法请求进行限制 。
对结果为空的查询给出默认值 。
可以使用布隆过滤器解决缓存穿透的问题
- 把已存在数据的key存在布隆过滤器中,相当于redis前面挡着一个布隆过滤器。
- 当有新的请求时,先到布隆过滤器中查询是否存在:
- 如果布隆过滤器中不存在该条数据则直接返回;
- 如果布隆过滤器中已存在,才去查询缓存redis,如果redis里没查询到则再查询Mysq|数据库
2 缓存击穿
- 对于某一个缓存 ,在高并发 情况下若其访问量特别巨大,当该缓存的有效时限到达时(即刚好达到设置的时间,此数据在缓存消失了 (key到期了),此时大量用户来访问它), 可能会出现大量 的访问都要重建该缓存
- 即这些访问请求发现缓存中没有该数据 ,则立即到DBMS 中进行查询,那么这就有可能会引发对 DBMS 的高并发查询 ,从而接导致 DBMS 的崩 溃。
- 这种情况称为缓存击穿 ,而该缓存数据称为热点数据 。 对于缓存击穿的解决方案,较典型的是使用"双重检测锁"机制。 类似于高并发下安全的单例
3 缓存雪崩
对于缓存中的数据,很多都是有过期时间的。
- 若大量缓存的过期时间在同一很短的时间 段内几乎同时到达,那么在高并发访问场景下就可能会引发对 DBMS 的高并发查询,而这将 可能直接导致 DBMS 的崩溃。这种情况称为缓存雪崩。
- 对于缓存雪崩没有很直接的解决方案,最好的解决方案就是预防,即提前规划好缓存的 过期时间。要么就是让缓存永久有效,当 DB 中数据发生变化时清除相应的缓存。
- 如果 DBMS采用的是分布式部署,则将热点数据 均匀分布在不同数据库节点中,将可能到来的访问负载均衡开来。
解决办法
事前:尽量保证整个 Redis 集群的高可用性,发现机器宕机尽快补上,选择合适的内存淘汰策略。
事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉, 通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
事后:利用 Redis 持久化机制保存的数据尽快恢复缓存
4 数据库缓存双写不一致
以上三种情况都是针对高并发读 场景中可能会出现的问题,而数据库缓存双写不一致问 题,则是在高并发写场景下可能会出现的问题。 对于数据库缓存双写不一致问题,以下两种场景下均有可能会发生:
(1) "修改 DB 更新缓存 "场景
对于具有缓存 warmup 功能的系统,DBMS 中常用数据的变更,都会引发缓存中相关数 据的更新。在高并发写请求 场景下,若多个请求要对 DBMS 中同一个数据进行修改 ,修改后 还需要更新缓存 中相关数据,那么就有可能会出现缓存与数据库中数据不一致的情况。
第一种没有问题。
第二种:a修改DB中的数据后stock=7,刚想从本地缓存 写入redis缓存,此时出问题了(有可能是时间片到了也有可能redis出现问题),这是b过来了,也修改了数据stock=2,然后更新缓存也是2,然后出现问题的a请求又好了,更新redis stock为7。(覆盖了2)
解决方案:
最经典的缓存+数据库读写的模式,就是 预留缓存模式Cache Aside Pattern。
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
- 更新的时候,先删除缓存 ,然后再更新数据库,这样读的时候就会发现缓存中没有数据而直接去数据库中拿数据了。
题外话:
本地缓存 和Redis缓存是两种不同的缓存方式。
本地缓存是指将数据缓存在本地内存 中,通常使用内存缓存库(如Memcached、Ehcache等)来实现。本地缓存的优点是访问速度快,缓存的数据可以快速地被读取,同时数据在本地内存中,可以减轻数据库的压力。然而,本地缓存的缺点是容易受到服务器重启、应用重启等因素的影响,同时可用空间有限。
Redis缓存是一种基于内存的(也可以持久化到磁盘)键值对存储系统,具有高性能、高可用性和可扩展性等优点。Redis可以作为分布式缓存来使用,可以减轻数据库的压力并提高应用程序的响应速度。Redis还提供了多种数据结构,如字符串、哈希、列表、集合和有序集合等,还可以设置过期时间,可以满足复杂的应用场景。相比之下,本地缓存通常只适用于单机环境,适用于只有一个应用程序使用的数据,而Redis适用于分布式、多应用程序场景下的数据缓存。虽然Redis的性能和可用性优于本地缓存,但是使用Redis也需要考虑到数据一致性、网络延迟等问题。因此,具体的缓存方案应该根据应用场景来选择。
(2) "修改 DB 删除缓存 "场景
在很多系统中是没有缓存 warmup 功能的,为了保持缓存与数据库数据的一致性,一般 都是在对数据库执行了写操作后,就会删除相应缓存。
在高并发读写请求 场景下,若这些请求对 DBMS 中同一个数据 的操作既包含写也包含读 , 且修改后还要删除缓存中相关数据,那么就有可能会出现缓存与数据库中数据不一致的情况。
问题:
- b先查询 redis中无数据 ,到DB查为10,然后写入本地缓存,此时出现问题中断了。
- 然后a过来修改DB中的数据,并将redis中相关数据删除。
- 然后b恢复执行,将本地缓存的10写入redis。
(3) 解决方案:延迟双删
延迟双删方案是专门针对于"修改 DB 删除缓存"场景的解决方案。但该方案并不能彻 底解决数据不一致的状况,其只可能降低发生数据不一致的概率。
延迟双删方案是指,在写操作完毕后会立即执行一次缓存的删除 操作,然后再停上一段 时间 (一般为几秒)后再进行一次删除 。而两次删除中间的间隔时长,要大于一次缓存写操作的时长。