Redis-day03|青训营笔记

168 阅读9分钟

说一说Redis的淘汰策略

若写入数据将导致超出maxmemory限制时,redis将会利用maxmemory-policy所指定的策略对内存中的数据进行淘汰,该策略总共有八个选项:

image.png

  • volatile代表redis将从设置了过期时间的数据里进行淘汰。
  • allkeys代表redis将从所有的数据中进行淘汰。
  • random代表redis将进行随机淘汰。
  • LRU代表redis将采用LRU算法进行淘汰。
  • LFU代表redis将采用LFU算法进行淘汰。
  • ttl代表存活时间,redis选择设置的存活时间最小的数据进行淘汰
  • noeviction代表不删除任何数据,redis直接返回错误

LRU

LRU算法(least recently Used),按照最近最少使用的原则对数据进行筛选和淘汰。其原理是将所有的数据组成一个链表,表头和表末尾分别代表LRU端和MRU端。新增的数据和刚刚被访问的数据都会被移动到MRU端,当链表的空间被占满,会删除LRU端的数据。

Redis采用的LRU算法是一种类LRU算法:它并没有将所有的数据组成一个链表,它的执行原理是这样的:

Redis在执行写入操作时,若发现将要超出maxmemory限制,且maxmemory-policy设置的策略是和LRU算法相关的策略,则Redis会执行一次LRU淘汰算法。这里的LRU淘汰算法是一种近似LRU算法,Redis惠济路每个记录最近一次访问的时间戳,近似LRU算法会采样N个key,而不是遍历所有的key,然后比较采样的key的最近一次访问的时间戳,然后淘汰掉最旧的key。

LRU的缺点

若一个数据不是热点数据,仅仅在刚刚被访问了一次,则LRU算法会误认为该数据为热点数据,短时间内不会被淘汰。

LFU

LFU(least Frequently Used)算法正是用于解决上述问题。在Redis利用LFU算法淘汰数据的时候,会根据key的访问次数进行淘汰。LFU在LRU的基础上,增加了一个计数器,来统计总的访问次数,当使用LFU进行淘汰时,首先会根据总的访问次数进行筛选,对总的访问次数最低的数据进行淘汰,如果总的访问次数相同,则根据最近访问的时间戳,对最近访问时间最久的数据进行淘汰。

介绍一下Redis的过期策略

Redis对于设置了过期时间的数据,在超过过期时间时,并不会立即将数据进行删除。

那这些过期的数据是如何删除的呢,Redis支持两种删除策略:

  • 惰性删除:客户端访问一个key的时候,首先会检查它的过期时间,如果超过了过期时间,则将其进行删除。

  • 定期删除:Redis将所有设置了过期时间的key放到一个字典中,并对该字典进行每秒10次的扫描。 扫描并不会遍历所有的key,而是采用了一种贪心策略:

    1. 在字典里随机选择20个key
    2. 删除这20个key中的过期的key
    3. 本次扫描的过期的key占百分之25以上,说明过期的key较多,重复步骤一。

缓存穿透、缓存击穿、缓存雪崩有什么区别,该如何解决?

缓存穿透

缓存穿透是什么

缓存穿透是指客户端大量查询一些根本不存在的数据,使得大量请求直达存储层,导致其负载过大,甚至宕机。出现这种情况的原因,可能是由于业务层误将缓存和数据库中的数据删除了。也有可能是有人恶意攻击,专门访问根本不存在的数据。

布隆过滤器

布隆过滤器是什么:布隆过滤器是一个一维的二进制向量,他可以用很小的代价估算出数据是否存在。 实现原理: 底层的数据结构:

 一个大型的一维二进制向量 
 若干不一样的哈希函数,每个哈希函数都能将数据算的比较均匀

添加原理:

  • 在添加key时,每个Hash函数都利用该key计算出一个Hash值,然后将一维二进制数组的这些Hash值对应的位置的值都设置为1。

查询原理:

  1. 在查询key的时候,每个Hash函数都利用该key计算出一个Hash值,然后查询一维二进制数组的这些Hash值对应的位置的值是否都为1。
  2. 如果这些Hash值对应的位置的值有一个为0,则这个布隆过滤器中一定没有存储该key。
  3. 如果这些Hash值对应的位置的值全都为1,则这个布隆过滤器中极有可能存储过该key。

应用场景

  1. 可以利用布隆过滤器解决缓存穿透问题。
  2. 在进行推送时,要判断该新闻是否已经被推送过,就可以利用布隆过滤器来解决。

缓存穿透如何解决

有两种方案:

  1. 缓存空对象:存储层未命中,但是仍将空值缓存到Redis中,客户端再次查询该数据时,缓存层会直接返回空值,这样就不会再去查询存储层了。
  2. 布隆过滤器:将数据存入布隆过滤器,访问缓存之前可以用布隆过滤器进行拦截,如果请求的数据不存在,直接返回空值。

缓存击穿

一份热点数据,它的访问量非常大。在其缓存失效的瞬间,大量请求直达存储层,导致服务崩溃。

解决方案:

  1. 设置热点数据永不失效。
  2. 对该热点数据的存储层进行访问时,加互斥锁,当一个线程访问了存储层,其他的线程只能等待。当这个线程访问结束以后,数据也就被加载到了内存中。其他线程在被放行以后,直接访问缓存层就可以获取数据了。

缓存雪崩

在某一时刻,可能是由于大量数据同时过期,也有可能是由于Redis节点发生故障,缓存层无法继续提供服务,大量请求直接打到存储层,导致存储层宕机。

解决方案:

  1. 设置热点数据永不过期。
  2. 设置过期数据的时候,要设置不同的过期时间,在过期时间上加一个随机数,防止大量数据同时过期。
  3. 构建高可用的Redis服务,采用哨兵或集群的架构,在单个Redis节点宕机以后,可以快速进行故障恢复。
  4. 缓存雪崩的关键在于监控。包括监控缓存命中率、后端系统负载,如果感知到发生缓存雪崩,及时进行降级、服务熔断。如果访问的不是核心数据,直接返回空值、或者预先设定好的信息、或者错误信息。

如何解决缓存和数据库的双写一致性

在数据进行修改的时候,不更新缓存数据,而是直接删除缓存数据。下一次查询缓存未命中,直接去数据库中去获取。采取该方式的原因有两点:

  1. 直接删除缓存更为简单。删除缓存是一个幂等的操作,无论执行多少次,都会得到相同的结果如果我们采用删除缓存,如果在数据库层面已经考虑了并发问题,就不需要在缓存层面再次考虑并发问题了。而更新缓存不是幂等的操作,我们需如果采用更新缓存的方式,还需要在缓存层面考虑并发问题,需要对后端程序加一些额外的锁,影响程序的性能,并提升复杂度。
  2. 直接删除缓存更安全。若出现缓存更新成功,数据库更新失败的情况,数据不一致但是却很难察觉。若出现缓存更新失败,数据库更新成功,数据不一致也很难察觉。

常用的保证缓存和数据库双写一致性的方法:延时双删

延时双删先删除缓存,再更新DB,再删除缓存

先删除缓存,可以保证在DB更新完以后查询时,第一时间将新数据加载到缓存中。但是如果出现在DB更新完成之前将DB的数据加载到缓存的情况,旧数据也就再次被加载到缓存中。而我们的缓存中想要的是新数据,因此我们需要在DB更新完毕后再次删除缓存。这次删除缓存之后加载的缓存,就一定是数据库中的新数据了。而我们具体不知道DB何时真正完成数据的更新,因此我们需要根据DB业务估计一个执行时间N,在更新DB操作触发后的N秒,在这个时间点再次删除缓存(这个时间点其实就是我们根据估计的业务时长估计出的DB数据更新完成的时间点)。

优化后的延时双删:将第二次延时删除改成基于cannal的精确删除。cannal伪装成MYSQL的从机,监听MYSQL的binlog二进制文件,当binlog发生更新,也就意味着DB更新数据成功,在此时向MQ发送一条消息,消费者不断重试,进行缓存的删除,MQ采用手动确认的机制,如果删除失败,则不确认,直到删除成功为止。

基于Redlock的分布式锁

为什么要有基于RedLock的分布式锁

对于建立在单个节点上的分布式锁,存在一个潜在问题。

A在主节点上加锁,主节点还没来得及将数据同步给从节点,主节点就已经挂掉了。从节点成为新的主节点后,B进程依然可以继续加锁。当A节点重启,重新变成主节点以后,就会出现一把锁被两个进程持有的情况,违背了锁的唯一性原则。

image.png

Redlock的分布式锁是如何实现的

可以直接使用Redisson框架进行实现。 单个主节点上的分布式锁,是无法保证高可用的,因此为了保证分布式锁的高可用,我们可以采用多节点的实现方案。

  1. 这些节点相互独立,不需要主从复制等协调机制。
  2. 加锁:加锁的时候进程向N个节点同时加锁,若超过半数的节点加锁成功,则加锁成功。
  3. 解锁:向N个节点发送DEl命令进行解锁。

image.png