缓存注意点

从广义上说,产生缓存雪崩的原因有两种:

​ 第一种是,缓存系统本身不可用,导致大量请求直接回源到数据库;

​ 第二种是,应用设计层面大量的 Key 在同一时间过期,导致大量的数据回源。

解决方案:

方案一,差异化缓存过期时间,不要让大量的 Key 在同一时间过期

方案二,让缓存不主动过期。初始化缓存数据的时候设置缓存永不过期,然后启动一个后台线程 30 秒一次定时把所有数据更新到缓存,而且通过适当的休眠,控制从数据库更新数据的频率,降低数据库压力(如果无法全量缓存所有数据则此方案行不通)

缓存穿透和缓存击穿的区别:

​ 缓存穿透是指,缓存没有起到压力缓冲的作用

​ 而缓存击穿是指,缓存失效时瞬时的并发打到数据库

对于高并发的缓存 Key 回源问题,可以使用锁来限制回源并发数;对于不存在的数据穿透缓存的问题,可以通过布隆过滤器进行数据存在性的预判,或在缓存中也设置一个值来解决。

缓存同步策略

  1. 先更新缓存,再更新数据库;

  2. 先更新数据库,再更新缓存;

  3. 先删除缓存,再更新数据库,访问的时候按需加载数据到缓存;

  4. 先更新数据库,再删除缓存,访问的时候按需加载数据到缓存。

对应的优缺点

“先更新缓存再更新数据库”策略不可行。数据库设计复杂,压力集中,数据库因为超时等原因更新操作失败的可能性较大,此外还会涉及事务,很可能因为数据库更新失败,导致缓存和数据库的数据不一致。

“先更新数据库再更新缓存”策略不可行。一是,如果线程 A 和 B 先后完成数据库更新,但更新缓存时却是 B 和 A 的顺序,那很可能会把旧数据更新到缓存中引起数据不一致;二是,我们不确定缓存中的数据是否会被访问,不一定要把所有数据都更新到缓存中去。

“先删除缓存再更新数据库,访问的时候按需加载数据到缓存”策略也不可行。在并发的情况下,很可能删除缓存后还没来得及更新数据库,就有另一个线程先读取了旧值到缓存中,如果并发量很大的话这个概率也会很大。

“先更新数据库再删除缓存,访问的时候按需加载数据到缓存”策略是最好的。虽然在极端情况下,这种策略也可能出现数据不一致的问题,但概率非常低,基本可以忽略。举一个“极端情况”的例子,比如更新数据的时间节点恰好是缓存失效的瞬间,这时 A 先读取到了旧值,随后在 B 操作数据库完成更新并且删除了缓存之后,A 再把旧值加入缓存

因此,针对缓存更新更推荐的方式是,缓存中的数据不由数据更新操作主动触发,统一在需要使用的时候按需加载,数据更新后及时删除缓存中的数据即可。

内存淘汰策略

从 Redis 服务端的角度来说,缓存系统可以保存的数据量一定是小于原始数据的。

首先,我们应该限制 Redis 对内存的使用量,也就是设置 maxmemory 参数;其次,我们应该根据数据特点,明确 Redis 应该以怎样的算法来驱逐数据。

从Redis 的文档可以看到[href:https://redis.io/topics/lru-cache] 当数据内存已到最大时再添加元素要不就直接返回错误,要不就采用淘汰策略( Eviction policies),常用的数据淘汰策略有:

  1. allkeys-lru,针对所有 Key,优先删除最近最少使用的 Key;

  2. volatile-lru,针对带有过期时间的 Key,优先删除最近最少使用的 Key;

  3. volatile-ttl,针对带有过期时间的 Key,优先删除即将过期的 Key(根据 TTL 的值);

  4. allkeys-lfu(Redis 4.0 以上),针对所有 Key,优先删除最少使用的 Key;volatile-lfu(Redis 4.0 以上),针对带有过期时间的 Key,优先删除最少使用的 Key。

清理过期数据

  1. 惰性删除:当你访问这个key的时候如果,如果这个key已经过期则进行清除

  2. 主动过期:如果仅仅靠被动过期,导致已经过期的key不再访问,会使这些key一直占用内存,浪费内存空间,所以redis也有主动过期的方式,每隔10秒,redis会做一下操作:

    1. 随机抽取20个过期属性的key。
    2. 删除已经过期的key。
    3. 如果超过25%的key已经过期了,就会重新执行第一个操作。

    从这个算法来说,整个redis内存中,过期的数据会控制在25%以内。

相关文章

https://www.infoq.cn/article/3L3zAQ4H8xpNoM2glSyi 插个眼业务数据redis 热点数据分析案例

https://github.com/xueqiu/rdr golang解析redis-rdb 拥有bigkey