• 作者:努力的达子
  • 分类: java

1.redis的过期键删除策略

  redis是key-value的数据库,我们可以设置Redis中缓存的key的过期时间,Redis的过期策略是指Redis中缓存的key过期了,Redis如何处理

  了解redis过期删除策略前,先了解一下三种过期删除策略

  1.定时删除策略:

    概念:在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。(创建定时器删除

    优点:对内存最友好:通过使用定时器,可以保证过期的键会尽可能快地被删除,释放所占内存

    缺点:对cpu最不友好:在过期键比较多的情况下,删除过期键这一行为可能会占用相当一部分cpu的时间,对服务器的响应时间和吞吐量造成影响。

  2.惰性删除策略:

    概念:放任键的过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。(使用的时候删除

    优点:对cpu最友好:只有在取出键的时候才会对过期键进行检查,即不需要cpu定期扫描,也不需要创建大量的定时器。

    缺点:对内存最不友好:如果一个键已经过期,但是后面不会被访问到的话,那么就一直保留在数据库中。如果这样的键过多,无疑会占用很大的内存

  3.定期删除策略:

    概念:每隔一段时间,程序就对数据库进行一次检查,删除里面过期的键。至于要删除多少过期键,以及要检查多少个数据库,则有算法决定。(定期扫描删除

    优点:

    • 定期删除每隔一段时间执行一次过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对cpu时间的影响;
    • 通过删除过期键,能有效的减少因为过期键而带来的内存浪费

    缺点:难以确定删除操作执行的时长和频率

    • 如果删除操作执行得太频繁,或者执行的时间太长,定期删除策略就会退化成定时删除,以至于占用太多cpu的执行时间;
    • 如果删除操作执行的时间太少,或执行时间太短,定期删除策略又会和惰性删除一样,出现内存浪费。

Redis过期删除策略:

  redis实际使用的过期键删除策略是定期删除策略惰性删除策略

  redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。除了定时遍历之外,它还会使用惰性策略来删除过期的 key,所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。定时删除是集中处理,惰性删除是零散处理。

通过配合使用这两种删除策略,服务器可以很好地合理使用cpu时间和避免浪费内存空间之间取得平衡。

  1.定期删除

  Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。

    1. 从过期字典中随机 20 个 key;
    2. 删除这 20 个 key 中已经过期的 key;
    3. 如果过期的 key 比率超过 1/4,那就重复步骤 1;

  同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。

  如果某一时刻,有大量key同时过期,Redis 会持续扫描过期字典,造成客户端响应卡顿,因此设置过期时间时,就尽量避免这个问题,在设置过期时间时,可以给过期时间设置一个随机范围,避免同一时刻过期。 

  a. 如何配置定期删除执行时间间隔

    redis的定时任务默认是每秒执行10次,如果要修改这个值,可以在redis.conf中修改hz的值。

    redis.conf中,hz默认设为10,提高它的值将会占用更多的cpu,当然相应的redis将会更快的处理同时到期的许多key,以及更精确的去处理超时。 hz的取值范围是1~500,通常不建议超过100,只有在请求延时非常低的情况下可以将值提升到100。

  b. 单线程的redis,如何知道要运行定时任务?

    redis是单线程的,线程不但要处理定时任务,还要处理客户端请求,线程不能阻塞在定时任务或处理客户端请求上,那么,redis是如何知道何时该运行定时任务的呢?

    Redis 的定时任务会记录在一个称为最小堆的数据结构中。这个堆中,最快要执行的任务排在堆的最上方。在每个循环周期,Redis 都会将最小堆里面已经到点的任务立即进行处理。处理完毕后,将最快要执行的任务还需要的时间记录下来,这个时间就是接下来处理客户端请求的最大时长,若达到了该时长,则暂时不处理客户端请求而去运行定时任务。

  2.懒惰删除

  过期键的惰性删除删除策略由db.c/expireIfNeeded函数实现,所有读写数据库的Redis命令在执行之前都会调用expireIfNeed函数对输入键进行检查:

    • 如果键已经过期,那么expireIfNeeded函数将键删除
    • 如果键未过期,那么expireIfNeeded函数不做操作

  命令调用expireIfNeeded函数过程如下图

另外因为每个被访问的键都可能被删除,所以每个命令都必须能同时处理键存在以及不存在的情况。 下图表示get命令的执行过程

2. aof/rdb和复制功能对过期键的处理

  rdb

    • 生成rdb文件:生成时,程序会对键进行检查,过期键不放入rdb文件。
    • 载入rdb文件:载入时,如果以主服务器模式运行,程序会对文件中保存的键进行检查,未过期的键会被载入到数据库中,而过期键则会忽略;如果以从服务器模式运行,无论键过期与否,均会载入数据库中,过期键会通过与主服务器同步而删除。
  • aof

    • 当服务器以aof持久化模式运行时,如果数据库中的某个键已经过期,但它还没有被删除,那么aof文件不会因为这个过期键而产生任何影响;当过期键被删除后,程序会向aof文件追加一条del命令来显式记录该键已被删除。
    • aof重写过程中,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的aof文件中。

  复制

    当服务器运行在复制模式下时,从服务器的过期删除动作由主服务器控制:

      • 主服务器在删除一个过期键后,会显式地向所有从服务器发送一个del命令,告知从服务器删除这个过期键;
      • 从服务器在执行客户端发送的读命令时,即使碰到过期键也不会将过期键删除,而是继续像处理未过期的键一样来处理过期键;
      • 从服务器只有在接到主服务器发来的del命令后,才会删除过期键。

2.redis的线程模型,单线程为什么快

  文件事件处理器

    redis 基于 reactor 模式开发了网络事件处理器,这个处理器叫做文件事件处理器,file event headler。其是单线程的,因此 redis 才叫做单线程的模型。采用 IO多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器来处理这个事件。

如果被监听的 socket 准备好执行 accept、read、write、close 等操作的时候,跟操作对应的文件事件就会产生,这时候文件事件处理器就会调用之前关联好的事件处理器来处理这个事件。

文件事件处理器是单线程模式运行的,但是通过IO多路复用机制监听多个 sokcet,可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了 redis 内部的线程模型的简单性。

  文件事件处理器的结构包含 4 个部分:

  1.多个socket
  2.IO多路复用程序
  3.文件事件分派器
  4.事件处理器
    4.1命令请求处理器:写数据到 redis
    4.2命令回复处理器:客户端要从 redis 读数据
    4.3连接应答处理器:客户端要连接 redis
多个 socket 可能并发地产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 sokcet,会讲socket放入一个队列中排队,每次从队列中取出一个 socket 给事件分派器,事件分派器再把 socket 给对应的事件处理器。

然后一个 socket 的时间处理完之后,IO多路复用程序才会将队列中的下一个 socket 给事件分派器。文件事件分派器会根据每个 socket 当前产生的事件,来选择对应的事件处理器来处理。

  客户端与 redis 通信的一次流程

在 redis 启动初始化的时候, redis 会将连接应答处理器跟 AE_READABLE 事件关联起来,接着如果一个客户端跟 redis 发起连接,此时会产生一个 AE_READABLE 事件,然后由连接应答处理器来处理跟客户端建立连接,在服务端创建客户端对应的sokcet,同时将这个socket的 AE_READABLE 事件跟命令请求处理器关联起来。

当客户端向 redis 发起请求(读、写)的时候,首先就会在 socket 产生一个 AE_READABLE 事件,然后由对应的命令请求处理器来处理。这个命令请求处理器就会从socket中读取请求相关数据,然后进行执行和处理。

接着 redis 准备好了给客户端的响应数据之后,就会将 socket 的 AE_WRITEABLE 跟命令回复处理器关联起来,当客户端准备好读取相应数据时,就会在 scoket 上产生一个 AE_WRITEABLE 事件,会由对应的命令回复处理器来处理,就是将准备好的相应数据写入socket,供客户端来读取。

命令回复处理器写完之后,就会删除这个 socket 的 AE_WRITEABLE 事件和命令回复处理器的关联关系。

为什么 redis 单线程模型效率也能这么高

  1. IO多路复用程序不负责处理事件,只是监听所有的socket产生的一个请求,之后将请求压入队列。其是非阻塞的。
  2. 事件处理器是基于纯内存操作的,性能非常高
  3. 单线程反而避免了多线程的频繁上下文切换问题

3.缓存雪崩,缓存穿透,缓存击穿

1.缓存穿透

  缓存穿透是指查询一个缓存中和数据库中都不存在的数据,导致每次查询这条数据都会透过缓存,直接查库,最后返回空。

  当用户使用这条不存在的数据疯狂发起查询请求的时候,对数据库造成的压力就非常大,甚至可能直接挂掉。这种情况的流程就变成下图这样了:

缓存穿透解决方案
  方案一:缓存空值(null)或默认值

  分析业务请求,如果是正常业务请求时发生缓存穿透现象,可针对相应的业务数据,在数据库查询不存在时,将其缓存为空值(null)或默认值(key-value对写成key-null)。需要注意的是,针对空值的缓存失效时间不宜过长,一般设置为5分钟之内。当数据库被写入或更新该key的新数据时,缓存必须同时被刷新,避免数据不一致。 

  方案二:业务逻辑前置校验

  在业务请求的入口处进行数据合法性校验(更严格的参数校验:如id长度限制等等),检查请求参数是否合理、是否包含非法值、是否恶意请求等,提前有效阻断非法请求。比如,根据年龄查询时,请求的年龄为-10岁,这显然是不合法的请求参数,直接在参数校验时进行判断返回。

  

方案三:使用布隆过滤器请求白名单

在写入数据时,使用布隆过滤器进行标记(相当于设置白名单),业务请求发现缓存中无对应数据时,可先通过查询布隆过滤器判断数据是否在白名单内,如果不在白名单内,则直接返回空或失败。

  1.缓存雪崩 

  缓存雪崩是指缓存同一时间大面积的失效,所以,后面所有的请求会落在数据库上,造成数据库短时间内承受大量的请求而崩掉。

  解决方案:
  • 缓存数据的时间设置随机,防止同一时间大量数据后期现象发生。
  • 给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。
  • 缓存预热
  • 互斥锁

 

转载自: https://www.cnblogs.com/wmd-l/p/17117802.html