Redis正常查询过程:
- 客户端发起查询数据的请求至查询系统
- 先在缓存中进行数据的查询
- 若存在则直接返回数据给客户端
- 若缓存中不存在查询的数据时,则继续在在数据库中进行查询
- 在数据库中进行查询
- 若存在要查询的数据,则将数据存入缓存中,并返回给客户端
- 若不存在,直接返回null给客户端
1.缓存穿透
1.1 产生原因
大量的高并发请求缓存和数据库中都没有的数据,导致每次请求都会请求到数据库,数据库压力瞬间骤增,从而压垮数据库(卡死或宕机)
实际场景:
- 数据原来存在,但是后来在缓存和数据库中被误删或清理掉了,但前端或前置的应用程序中还存在相关数据
- 恶意攻击,利用不存在的key或者恶意尝试导致产生大量不存在的数据业务请求
1.2 解决方案
1.2.1 业务层校验:
对于明显不符合的数据进行判断,直接返回错误请求
如:key本身规则校验、用户校验、登录状态、n秒内访问接口的次数限制等
1.2.2 不存在的数据设置短过期时间处理
对于数据库中不存在的数据,可以考虑先将这个空数据存入缓存,同时根据实际业务进行短过期时间的设定处理,过期时间要尽可能段短
缺点:
-
- 需要缓存层提供更多的内存来缓存这些对象,空对象多的时候会造成缓存内存的浪费,被恶意攻击时可能出现Redis内存占用不足的情况
- 短期内缓存与数据库中的数据可能存在不一致的情况
1.2.3 使用布隆过滤器
布隆过滤器是一中可以利用极小的内存判断大量的数据“一定不存在或者可能存在”的数据结构;
布隆过滤器BloomFilter是根据hashcode判断的,不同的字符串的hashcode可能相同,如果某个hashcode存在,它对应的字符串不一定是你想要的那个字符串
利用布隆过滤器,将查询的数据条件都哈希到一个足够大的布隆过滤器中,用户发送的请求过来时,先被布隆过滤器拦截,一定不存在的数据直接就被拦截返回了,从而可以避免缓存和数据库都不存在的数据进行查询访问,达到减轻此类情况下数据库压力的目的。
注:布隆过滤器是有一定的误差,所以一般需要配合一些接口流量的限制(规定用户在一段时间内访问的频率)、权限校验、黑名单等来解决缓存穿透的问题
1.2.4 实时监控
对Redis进行实时监控,当Redis命中率降低的时候进行排查,配合运维同事对访问对象和请求数据进行分析,必要时拉入黑名单处理(拒绝恶意攻击)
注:当黑名单太大时,存放起来很耗费空间,也可以考虑在布隆过滤器中实现黑名单功能
2.缓存击穿
2.1 产生原因
缓存中某个过期的热点数据在未重新载入缓存之前被大量访问,大量请求瞬间穿过缓存直接请求数据库,导致数据库压力瞬间骤增,请求阻塞或者数据库崩溃宕机
实际场景:
- 平台双十一特价商品信息缓存在0点时正好失效,大量的用户又同时正好访问这个商品,造成大量的请求同时到达数据库。
2.2 解决方案
2.2.1 设置热点数据永不过期
对于某个频繁被访问的数据,缓存在Redis中,且设置key永不过期;
缺点:简单粗暴,对于某些业务场景不适用——热点数据越来越多时
2.2.2 定期更新key
启动定时任务,在热点数据的过期之前,定时更新热点数据,并重新更新其过期时间
2.2.3 使用锁机制(互斥锁/分布式锁)——常用方法
在缓存中获取key1数据时,若key1数据不存在,先将key1数据锁定,然后从数据库中进行数据获取,从数据库中查询完之后,再释放锁。在此期间,如有其他线程也获取key1数据时,发现获取锁失败,则睡眠一段时间之后再重试。
缺点:
- 代码复杂度增大
- 存在死锁的风险
- 存在线程池阻塞的风险
3.缓存雪崩
3.1 产生原因
Redis大量key在同一时间集体过期,或者Redis直接宕机,大量请求直接请求数据库,导致数据库压力瞬间骤增,甚至数据库崩溃宕机
3.2 解决方案
3.2.1 将key的过期时间分散开——以防止key集体过期
- 使用自动生成的随机数使得key的过期时间是随机的;
- 或者根据业务统一规划有效期,使得过期时间均匀分布
缺点:在一定程度上增加了维护的成本
3.2.2 构建缓存高可用集群——针对单点故障
引入redis集群,使用主从加哨兵,当一台或几台故障时,整个系统依然可以对外提供服务,提高服务的可用性
3.2.3 给业务添加多级缓存
使用nginx缓存+redis缓存+其他缓存,不同层使用不同的缓存,可靠性更强
3.2.4 设置缓存标记
记录缓存数据是否过期,过期会触发通知另外的线程在后台去更新实际的key
3.2.5 监控数据,适时调整过期时长
监控热门数据,实时调整热门数据的过期时长
3.2.6 数据预热
对于即将来临的大量请求,将数据提前缓存进Redis中,并设置不同的过期时间
3.2.7 使用锁机制
同2.2.3
4 拓展
4.1 布隆过滤器
布隆过滤器是一中可以利用极小的内存判断大量的数据“一定不存在或者可能存在”的数据结构;
实际上是一个很长的二进制向量和一系列随机映射函数。(由一个长度为m bit的位数组与n个hash函数组成的数据结构,位数组中每个元素的初始值都是0。在初始化布隆过滤器时,会先将所有key进行n次hash运算,这样就可以得到n个位置,然后将这n个位置上的元素改为1。这样,就相当于把所有的key保存到了布隆过滤器中了)
可以理解为一个不怎么精确的 set 结构,当你使用它的 contains 方法判断某个对象是否存在时,它可能会误判。但是布隆过滤器也不是特别不精确,只要参数设置的合理,它的精确度可以控制的相对足够精确,只会有小小的误判概率。
因为不同的字符串的hashcode可能相同,布隆过滤器BloomFilter是根据hashcode判断的,如果某个hashcode存在,它对应的字符串不一定是你想要的那个字符串;但是,hashcode不存在时,你所要的字符串,肯定不存在
优点:
-
- 时间复杂度低,增加和查询元素的时间复杂为O(N)(N为哈希函数的个数)
- 保密性强,布隆过滤器全量存储但不存储元素本身
- 存储空间小,如果允许存在一定的误判,布隆过滤器是非常节省空间的(相比其他数据结构如Set集合)
缺点:
-
- 有点一定的误判率,但是可以通过调整参数来降低
- 无法获取元素本身——没有存储元素本身
- 很难删除元素——布谷鸟过滤器
注:布隆过滤器很难删除元素的原因:
我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。
- 应用:
-
- 解决Redis缓存穿透问题
- 邮件过滤,使用布隆过滤器来做邮件黑名单过滤
- 对爬虫网址进行过滤,爬过的不再爬
- 解决新闻推荐过的不再推荐(类似抖音刷过的往下滑动不再刷到)
- HBase\RocksDB\LevelDB等数据库内置布隆过滤器,用于判断数据是否存在,可以减少数据库的IO请求
4.2 布谷鸟过滤器
布谷鸟过滤器
Redis使用布谷鸟过滤器
4.3 redis高可用的三种模式:主从模式,哨兵模式,集群模式
4.3.1 主从模式
redis多机器部署时,机器节点会被分成主节点(master节点)和从节点(slave节点)。一般主节点可以进行读、写操作,而从节点只能进行读操作。当主节点的数据发生变化时,会将变化的数据同步给从节点,这样从节点的数据就可以和主节点的数据保持一致了。
一个主节点可以有多个从节点,但是一个从节点会只会有一个主节点,也就是所谓的一主多从结构。
缺点:
-
- Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复;
- 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性;
- 如果多个Slave断线了,需要重启的时候,尽量不要在同一时间段进行重启。因为只要Slave启动,就会发送sync请求和主机全量同步,当多个Slave重启的时候,可能会导致Master IO剧增从而宕机。
- Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂;
- redis的主节点和从节点中的数据是一样的,降低的内存的可用性
4.3.2 哨兵模式
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器;
- 当哨兵监测到master宕机,会自动将slave切换到master,然后通过发布订阅模式通过其他的从服务器,修改配置文件,让它们切换主机;
- 然而一个哨兵进程对Redis服务器进行监控,也可能会出现问题,为此,我们可以使用多个哨兵进行监控(一般为了便于决策选举,使用奇数个哨兵)。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
哨兵可以和redis机器部署在一起,也可以部署在其他的机器上。多个哨兵构成一个哨兵集群,哨兵之间也会相互通信,检查哨兵是否正常运行,同时发现master宕机哨兵之间会进行决策选举新的master
工作原理:哨兵的工作原理是每个哨兵会以每秒钟 1 次的频率,向已知的主服务器和从服务器,发送一个 PING 命令。如果最后一次有效回复 PING 命令的时间,超过了配置的最大下线时间(Down-After-Milliseconds)时,默认是 30s,那么这个实例会被哨兵标记为主观下线。如果一个主服务器被标记为主观下线,那么正在监视这个主服务器的所有哨兵节点,要以每秒 1 次的频率确认主服务器是否进入了主观下线的状态。如果有足够数量(quorum 配置值)的哨兵证实该主服务器为主观下线,那么这个主服务器被标记为客观下线。此时所有的哨兵会按照规则(协商)自动选出新的主节点服务器,并自动完成主服务器的自动切换功能,而整个过程都是无须人工干预的。
4.3.3 集群模式
有多个主节点同时每个主节点有多个从节点,将数据分布在不同的主服务器上,以此来降低系统对单主节点的依赖,并且可以大大提高 Redis 服务的读写性能。Redis 集群除了拥有主从模式 + 哨兵模式的所有功能之外,还提供了多个主从节点的集群功能,实现了真正意义上的分布式集群服务
总结:
- 主从模式:master节点挂掉后,需要手动指定新的master,可用性不高,基本不用。
- 哨兵模式:master节点挂掉后,哨兵进程会主动选举新的master,可用性高,但是每个节点存储的数据是一样的,浪费内存空间。数据量不是很多,集群规模不是很大,需要自动容错容灾的时候使用。
- 集群模式:数据量比较大,QPS要求较高的时候使用。
4.4 锁机制
4.4.1 互斥锁
在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
4.4.2 Redis实现分布式锁
插入lockkey并设置过期时间,通过设置lockkey 或者 通过当前的时间与锁设置的时间做比较,如果当前时间已经大于锁设置的时间临界,即可以进一步判断是否可以获取锁,否则说明该锁还在被占用,不能获取该锁
具体过程:
- 当A通过setnx(lockkey,currenttime+timeout)命令能成功设置lockkey时,即返回值为1,过程与原理1一致;
- 当A通过setnx(lockkey,currenttime+timeout)命令不能成功设置lockkey时,这是不能直接断定获取锁失败;因为我们在设置锁时,设置了锁的超时时间timeout,当当前时间大于redis中存储键值为lockkey的value值时,可以认为上一任的拥有者对锁的使用权已经失效了,A就可以强行拥有该锁;具体判定过程如下;
- A通过get(lockkey),获取redis中的存储键值为lockkey的value值,即获取锁的相对时间lockvalueA
- lockvalueA!=null && currenttime>lockvalue,A通过当前的时间与锁设置的时间做比较,如果当前时间已经大于锁设置的时间临界,即可以进一步判断是否可以获取锁,否则说明该锁还在被占用,A就还不能获取该锁,结束,获取锁失败;
- 步骤4返回结果为true后,通过getSet设置新的超时时间,并返回旧值lockvalueB,以作判断,因为在分布式环境,在进入这里时可能另外的进程获取到锁并对值进行了修改,只有旧值与返回的值一致才能说明中间未被其他进程获取到这个锁
- lockvalueB == null || lockvalueA==lockvalueB,判断:若果lockvalueB为null,说明该锁已经被释放了,此时该进程可以获取锁;旧值与返回的lockvalueB一致说明中间未被其他进程获取该锁,可以获取锁;否则不能获取锁,结束,获取锁失败。