缓存问题总结
前言
通常应用中会使用数据库来作为数据存储,但是数据库面向磁盘,磁盘的读写速度比较慢,不合适处理高并发的应用场景,为了避免高并发场景下瞬间大量访问DB导致数据库系统瘫痪的问题,我们通常会使用缓存来提高系统支持高并发的能力。缓存一般都是基于内存的数据库,读写效率相比于磁盘读写都有大幅提升,但引入缓存后相应也会引入一些缓存相关的问题。
缓存问题
- 缓存穿透
- 缓存击穿
- 缓存血崩
- 缓存溢出
- 数据丢失
下面我们以redis为例,来分析这些问题并给出对应的解决方案。
问题分析
缓存穿透
缓存穿透指的是数据源(通常是DB)中,没有key对应的数据源,因此请求这类不存在的key,在缓存中获取不到的话就需要直接请求数据源,请求并发量大的话就会给数据源造成压力和风险。
针对这类问题,有两个常用的解决方案:
1.即使查询数据源没有该key对应的值,也在缓存中缓存该key,并设置空的value值。需要注意的是,必须给这种key设置一个短的过期时间,在这段时间内数据源新增了这个key对应的记录但是获取不到的情况。由此可见,该方案存在一个问题就是新增的记录可能存在不能立即生效的问题。
2.第二种方案是使用布隆过滤器,将可能的key值数据取哈希存储到一个bitmap中,通过判断key值是否在bitmap内来避免向数据源请求不存在的记录。
缓存击穿
缓存击穿和缓存穿透不同,缓存穿透是请求不存在key记录,而缓存击穿请求的key在数据源中是有对应的记录的。对于热点数据的key,如果刚好在缓存过期的时间后有大量并发请求,就会给数据源造成服务崩溃风险。
针对这一问题,通常的方案是加互斥锁,只有获取锁的线程可以请求数据源,其他请求可以等待重试获取缓存。
对于redis来说,可以通过SETNX
操作来设置一个互斥key来实现互斥锁,设置成功则代表成功获取锁。需要注意的是需要给互斥key来设置一个过期时间,避免死锁问题。
此外,也可以设置热点数据不过期来解决这类问题。
缓存雪崩
缓存击穿是针对某一个key过期时的并发问题,而缓存雪崩是缓存服务器重启或者大量缓存集中在一个时间段过期,导致大量并发请求直接打到数据源,造成数据源服务崩溃。
缓存雪崩的几个解决方案:
- 采用随机的过期时间。可以在设置缓存过期时间时,尽量分散各个key的过期时间避免集中过期的场景
- 使用锁或者队列。通过锁或者队列来避免所有请求直接请求数据库。这种会导致线程阻塞,可能影响用户体验。
- 缓存标记。通过记录缓存数据是否过期,如果过期则通知专门的线程来处理实际key的更新。
缓存溢出
缓存使用的是内存资源,相比于磁盘存储来说可以缓存的数据有限的。缓存溢出是指缓存的数据达到上限,无法缓存更多的数据。
这种问题的解决方案通常是给缓存设置一定的淘汰策略,一般使用LRU(Least Recently Used)。最近最少使用的key会被优先淘汰。
数据丢失
缓存数据在内存中,如果缓存服务器宕机或者重启,内存中的数据就会丢失。
针对这一问题,主要依赖于缓存服务的持久化。redis提供了两种数据持久化的方案,一种是RDB,一种是AOF。
RDB是在指定的时间间隔内生成数据集的时间点快照,是一个非常紧凑的文件,适用于灾难恢复,同时数据备份时只需要分配一个子进程来处理数据保存工作,父进程无需执行磁盘IO操作,可以最大化Redis的性能。
AOF的话可以让Redis非常耐久,尽可能短的减少数据丢失问题。但是备份文件体积要大于RDB,性能及恢复效率上不如RDB。
转载请注明出处,如有错误的地方请留言给我更正,谢谢!