1.缓存更新

1.1缓存更新策略

  • 内存淘汰:
    • 不需要自己维护,利用Redis的内存淘汰机制,当内存不足时自动淘汰部分数据,下次查询时更新缓存
    • 一致性 : 差
    • 维护成本:无
  • 超时删除:
    • 给缓存数据添加TTL时间,到期后自动删除缓存,下次查询时更新缓存
    • 一致性 :一般 (如果数据库中的值发生更新,但是缓存中的数据仍未过期,会出现数据不一致问题)
    • 维护成本:低
  • 主动更新:
    • 在修改数据库的同时,进行更新缓存的操作
    • 一致性:好 (也有可能出现数据不一致)
    • 维护成本:高

业务场景:

  • 低一致性需求:使用内存淘汰机制。如一些更改频率很低的数据的缓存查询
  • 高一致性需求:主动更新,并以超时删除作为辅助备用方案。如频繁修改的数据的缓存查询

1.2 主动更新策略需要考虑的问题

1.删除缓存还是更新缓存?

  • 更新缓存: 每次更新数据库都更新缓存,无效写操作较多 (比如更新1000次数据库同一个数据,那么缓存也更新了1000次,实际上只有最后一次更新缓存是有效的) 适合于读多写少
  • 删除缓存: 更新数据库时让缓存失效(删除缓存 or 让缓存过期 ),查询时再更新缓存 (常用方式)

2.如何保证缓存与数据库的操作同时成功或失败?(原子性)

  • 单体系统: 将缓存与数据库操作放在一个事务

  • 分布式系统:利用TTC等分布式事务方案

3.先操作缓存还是先操作数据库?

  • 先删除缓存,再更新数据库

可能存在的问题:多线程环境下,(操作数据库耗时长于操作缓存)很大概率发生 线程2线程1 删除缓存还未来得及更新数据库时,就进行查询操作,然后未命中查询数据库一个旧的数据写入缓存,导致出现数据一致性问题

在这里插入图片描述

  • 先更新数据库,再删除缓存 (常用方式)

可能出现的问题:在线程1查缓存时,此时缓存恰好失效,然后查数据库,还未来得及写入缓存,线程2进行了更新数据库的操作,然后线程1将旧的数据写入缓存,导致数据不一致性的问题(由于操作缓存效率更高,这种问题发生的概率比较小)

在这里插入图片描述

2.缓存问题

2.1缓存穿透

场景: 查询不存在的数据,使得请求一直查询数据库,在大量请求下导致数据库负载过大,甚至宕机

解决方案:

  • 缓存空对象

存储层未命中后,仍然将空值存入缓存,再次访问该数据时,缓存直接返回空值 (空值是“”)

优点: 实现简单,维护方便

缺点: 额外的内存消耗 (可以给空值设置TTL过期时间)

​ 可能造成短期的不一致 (在缓存空值之后,数据库新增了这个数据)

  • 布隆过滤器

将所有存在的key提前存入布隆过滤器(二进制形式),在访问缓存层之前,先通过过滤器拦截,若请求的是不存在的key,则直接返回空值

优点: 内存占用较少,没有多余key

缺点: 实现复杂 存在误判可能

在这里插入图片描述

主动的应对方案:

  • 增强id的复杂度,避免被猜测id规律

  • 做好数据的基础格式校验

  • 加强用户权限校验

  • 做好热点参数的限流

2.缓存击穿

场景: 热点数据,高并发访问。在其缓存失效瞬间,大量请求直达存储层,导致数据库压力剧增,服务崩溃
在这里插入图片描述

解决方案:

  • 加互斥锁

对数据的访问加互斥锁,当一个线程访问该数据时,其他线程只能等待,这个线程访问过后,缓存中的数据将被重建,此时其他线程可以直接从缓存中取值

优点: 保证一致性 实现简单

缺点: 线程需要等待,性能受影响,可能有死锁风险(在多缓存场景下多个锁可能导致死锁)

在这里插入图片描述

使用Redis中的setnx模拟互斥锁的过程

private boolean tryLocal(String key) {
    // setnx 存入一个key 值可以随意 设置10s的过期时间
    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
    return BooleanUtil.isTrue(flag);

}

private void unlock(String key) {
    stringRedisTemplate.delete(key);
}
  • 逻辑过期

不设置过期时间,添加一个expire属性 值为当前时间+过期时间 转为整数(时间戳)

为每个value设置逻辑过期时间,当发现该值逻辑过期时,使用单独的线程重新缓存,其他线程返回旧的值

优点: 线程无需等待,性能较好

缺点: 不保证一致性(返回旧数据),有额外内存消耗,实现复杂

在这里插入图片描述

3.缓存雪崩

场景: 当缓存失效 (如同一时段大量的key过期或者redis挂了),导致所有请求直达存储层,造成存储层宕机

解决方案:

  • 避免同时过期

设置过期时间,附加一个随机数,避免大量的key同时过期

  • 构建高可用的redis缓存

部署多个redis实列,个别结点宕机,依然可以保持服务的整体可用 (Redis集群)

  • 构建多级缓存

增加本地缓存,在存储层前面多加一级屏障,降低请求直达存储层的几率 (比如 caffieine本地缓存 、Nginx缓存)

  • 启用限流和降级措施

存储层增加限流措施,当请求超出限制时,对其提供降级服务

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐