赞
踩
用户频繁的发起恶意请求查询缓存中和数据库中都不存在的数据,查询积累到一定量级导致数据库压力过大甚至宕机。
比如正常情况下用户发起一个请求根据某个主键ID获取数据库信息,服务器接收到请求后会先查询缓存,如果缓存命中则返回,未命中就去查询数据库,数据库中存在则返回并存入Redis,不存在提示或者报错。
但是假如有人使用一定不存在的ID,比如负数或者很大的数值进行恶意频繁的请求,就会导致数据库的压力增大甚至宕机。
对于那些缓存中和数据库中都查不到的数据,同样把它保存到缓存中并将value值设置为null,这样下次同样的id再来请求就直接从缓存中获取null值并返回。
优点:实现简单。
缺点:浪费内存,并且如果攻击者每次使用的是不同的脏数据进行攻击,这种方式是处理不了的。
使用布隆过滤器服务器的逻辑就会变成这样:
什么是布隆过滤器呢?
先来看下面这张图:
布隆过滤器的底层是一个bitmap,也就是一个数组,每个下标只存储0或1,默认初始化时全部都为0。可以在其中定义多个不同的哈希函数对要存储的数据进行计算,不同的哈希函数计算出的结果作为数组的下标将其对应的值改为1。这样不管是存数据还是查询数据都可以通过哈希计算得到对应下标然后根据是否为1判断是否存在,如果有一个值不为1则就是不存在。
但是,这种机制也是会出现一定的误判率的,具体看下面这张图:
首先将id1和id2存入布隆过滤器中,得到了对应的下标并设置为1,这个时候id3也要存进来,然后通过哈希计算得到了3、9、12的下标,这个时候虽然id3并不存在,但是布隆过滤器也会判定为存在!这个就是误判。
在Redis缓存中某条热点数据过期了,然后在这一时间有大量的并发请求到服务器导致缓存中查不到,就都请求到了数据库,数据库压力瞬间增大甚至宕机。
缓存穿透的概念基本就是它的出现的原因,理论上当在数据库查到数据时会将数据放到缓存,后续请求就可以从缓存中获取到,但是缓存穿透发生的节点就是在数据库查到数据之后和将数据放回缓存之前这一时间段。
逻辑过期时间类似于Mybatis的逻辑删除,可以对缓存中的数据增加一个expire属性,对应的值就是过期时间。这样当请求过来之后,从缓存中获取到数据判断是否逻辑过期了,如果逻辑过期了就创建一个线程进行缓存重建,注意,这里需要加上互斥锁进行,新开线程是为了不阻塞主线程,而加互斥锁是防止后续的线程也进行缓存重建流程,在缓存重建完成之前主线程都就会直接返回这条过期数据给用户。
**缓存重建:**查询数据库,并重新放入缓存并设置新的逻辑过期时间。
优点:可以保证高可用性。
缺点:在过期重建期间,返回的数据是已经过期的,不能保证数据的完全一致性。
当缓存中查询不到该数据时,先获取互斥锁(可以利用redis分布式锁),然后再去查询数据库并将数据重新放入缓存和设置过期时间,最后释放锁。
优点:保证数据的完全一致性。
缺点:性能差,加锁后其它线程只能等待。
下面是两种方式的图解对比:
大量的热点数据在同一时间段缓存过期或Redis宕机了,导致大量请求到达数据库,带来巨大压力。
缓存雪崩的主要原因有两种,一种是大量的key都设置了同一过期时间,如果在数据过期是出现大量的请求就会导致数据库压力过大;另一种是Redis直接宕机了,那么所有的请求到会直接到达数据;
在给不同的key设置过期时间的时候,可以设置一个随机值比如1-5分钟时间,让过期时间加上这个随机值作为key的过期时间,这样就避免了在同一时间大量的key同时过期。
什么是双写一致?按照正常逻辑读数据时会先查询缓存,如果缓存未命中才会去查数据库;写数据时则需要数据库的数据和缓存中的数据保持一致。正常情况下我们先删除缓存,再更新数据库并重新放回缓存是没有任何问题的。但是在分布式场景下或者并发场景下,这种方式就会导致在写数据时的数据不一致问题。
比如下面的两种情况都会导致数据不一致性问题:
而保证写数据时数据库和缓存中的数据保持完全一致,也就是所说的双写一致。
既然先删缓存再写数据库和先写数据库再删缓存都不行,那我们就删两次,在写数据库之前删一次缓存,写完之后再删一次;这种方式基本上可以解决问题,但是写完之后我们不能立马删除缓存,而是需要延迟一会儿再删除,为什么呢?因为大部分公司数据库都采用了主从的模式,那么刚更新数据库后需要同步给从数据库,这个时间点如果立马删除缓存数据可能还是会导致数据不一致性(一般主数据库接收写操作,从数据库接收读操作),所以要延迟一会儿再进行删除。
但是因为这个延迟时间并不好控制,所以延迟双删在存在数据库主从模式的架构下是不建议使用的,它不能保证数据库的完全一致性。
首先先来看下具体的实现思路:
可以利用读写锁的方式,在写数据的时候我们加上排他锁,也就是写数据时其他线程是不可以进行读和写操作的。而在读数据时是加上共享锁,使得其他线程可以并发读取数据。
读写锁的方式既保证了数据的强一致性,又保证了效率。那么具体实现方式可以使用Redisson来实现。
读锁代码:
写锁代码:
具体实现思路如下图:
首先需要加一个中间层,也就是MQ;在进行数据库写的操作时需要将这条消息发送到MQ中,然后再写一个监听这条消息的方法,去进行缓存的更新操作。那么这样就可以保证更新完数据以后缓存也会实时的进行更新,而可靠性也就相当于交给了MQ了。
优点:保证了数据最终一致性,并且不影响效率。
缺点:在同步MQ和MQ进行更新操作期间,用户读到的数据不是最新的。
Redis实现持久化的方式有两种,一种是RDB的形式,一种是AOF的形式,一般公司项目中都会使用两种方式结合的形式保证数据的持久化。
什么是持久化?持久化其实说白了就是将数据写到磁盘中,因为Redis是基于内存的,所以数据都是放在内存中,如果Redis崩溃了,那么就会导致所有的数据丢失,这是一个严重的问题!
为了解决这种问题,就可以将内存中的数据写入到磁盘中,一旦Redis崩溃了,那我们还可以在重启Redis时将磁盘中保存好的数据进行回复,而这种操作就被称为持久化。
那么接下来我们需要思考下面几个问题:
RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照,简单来说就是把内存中的所有数据都记录到磁盘中,默认会创建一个名为 dump.rdb 的文件保存数据,当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。
在Redis配置文件中可以通过 dbfilename 属性查看RDB的文件名:
Redis的RDB持久化操作可以在以下两种情况下触发:
需要注意的是,在自动触发RDB持久化时,Redis也会选择 bgsave 而不是 save 来进行持久化操作,以避免阻塞主线程。
打开本地的Redis配置文件,可以看到Redis有下面默认配置:
这里有三个save参数:
当设置了多个save参数时,Redis会按照从上到下的顺序依次检查每个save参数的条件是否满足。如果某个save参数的条件被满足(即时间间隔内数据的变化量达到了指定的数量),Redis就会执行一次RDB持久化操作。这意味着,只要有一个save参数的条件被满足,Redis就会进行持久化操作。
AOP,即Append Only File,是Redis的一种持久化方式。与RDB(Redis Database)快照模式不同,AOP模式是通过记录每一条修改数据的命令,并将这些命令追加到AOF文件中来实现数据的持久化。当Redis服务器重启时,它会通过重新执行AOF文件中的命令来恢复数据。
AOP模式的核心原理是“先执行命令,后写日志”。每当Redis执行一个修改数据的命令时,它都会将该命令写入AOF文件。这种方式的优点是可以确保数据的实时性和准确性,因为即使在系统崩溃的情况下,也不会丢失任何已经写入AOF文件的命令。
然而,AOP模式也存在一些不足之处:
为了解决AOF文件体积过大的问题,Redis引入了AOP重写机制。AOP重写是将Redis进程内的数据转化为写命令同步到新AOF文件的过程。在重写过程中,Redis会遍历当前内存中的所有数据,并生成相应的写命令,然后将这些命令写入新的AOF文件中。重写后的AOF文件体积会大大减小,但需要注意的是,在重写过程中,Redis的写操作可能会受到一定的影响。
重启 Redis 时,我们很少使用 rdb 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。
Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。 将 rdb 文 件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是自 持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小。于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可 以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。
1、从过期字典中随机 20 个 key;
2、删除这 20 个 key 中已经过期的 key;
3、如果过期的 key 比率超过 1/4,那就重复步骤 1;
同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认上述操作不会超过 25ms。
Redis的淘汰策略(Eviction Policy)定义了当Redis内存使用达到其最大限制(由maxmemory配置指令设置)时,Redis应该如何处理新的写请求(如SET、LPUSH等)。以下是Redis的八种淘汰策略的详细说明:
volatile-xxx 策略只会针对带过期时间的 key 进行淘汰,allkeys-xxx 策略会对所有的 key 进行淘汰。如果你只是拿 Redis 做缓存,那应该使用 allkeys-xxx,客户端写缓存时 不必携带过期时间。如果你还想同时使用 Redis 的持久化功能,那就使用 volatile-xxx 策略,这样可以保留没有设置过期时间的 key,它们是永久的 key 不会被 LRU 算法淘汰。
Redis 为实现近似 LRU 算法,它给每个 key 增加了一个额外的小字段,这个字段的长度是 24 个 bit,也就是最后一次被访问的时间戳。
上面提到处理 key 过期方式分为定时集中处理和懒惰处理,LRU 淘汰不一样,它的处理 方式只有懒惰处理。当 Redis 执行写操作时,发现内存超出 maxmemory,就会执行一次 LRU 淘汰算法。这个算法也很简单,就是随机采样出 5(可以配置) 个 key,然后淘汰掉最旧的 key,如果淘汰后内存还是超出 maxmemory,那就继续随机采样淘汰,直到内存低于 maxmemory 为止。
如何采样就是看 maxmemory-policy 的配置,如果是 allkeys 就是从所有的 key 字典中 随机,如果是 volatile 就从带过期时间的 key 字典中随机。每次采样多少个 key 看的是 maxmemory-samples 的配置,默认为 5。
同时 Redis3.0 在算法中增加了淘汰池,进一步提升了近似 LRU 算法的效果。 淘汰池是一个数组,它的大小是 maxmemory_samples,在每一次淘汰循环中,新随机出 来的 key 列表会和淘汰池中的 key 列表进行融合,淘汰掉最旧的一个 key 之后,保留剩余较旧的 key 列表放入淘汰池中留待下一个循环 。
lru 和 lfu 的区别:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。