【程序员必备】缓存三兄弟之穿透、击穿、雪崩

System.out.println("....................阿弥陀佛.....................");
System.out.println("                    _oo0oo_                    ");
System.out.println("                   o8888888o                    ");
System.out.println("                    88” . “88                    ");
System.out.println("                   ( |- _ - | )                    ");
System.out.println("                   O \\  =  / O                    ");
System.out.println("                  __/ ‘---’ \\__                    ");
System.out.println("                  '\\|       |/'                    ");
System.out.println("              / \\\\|||   :   |||// \\                    ");
System.out.println("            / _|||||  -卍- |||||_  \\                    ");
System.out.println("           |    |\\\\\\   -    ///  |_ \\                     ");
System.out.println("           |  \\_|  ''\\---/''    |_/  |                    ");
System.out.println("           \\ ,-\\__    '-'    __/- ,  /              ");
System.out.println("          ___'.  .'  /--.--\\   '.  .'___               ");
System.out.println("          .”“'< ‘.___\\_<|>_/___.'> ' ”“.             ");
System.out.println("         | | : '-\\'.:'\\ _ /' : .'/ - ': | |            ");
System.out.println("          \\ \\ '_. \\_ __\\ /__ _/  , - ' / /           ");
System.out.println("     ====='-.___'.___ \\____//___.-' ___.-' =====           ");
System.out.println("                      '=----='           ");
System.out.println("                                                             ");
System.out.println("....................佛祖保佑,永无BUG.....................");

1. 缓存穿透问题

背景:当前端发送请求的时候,缓存未命中,数据库也未命中,导致数据无法添加到缓存中,前端请求一直将到达数据库,造成数据库压力过大 解决方案:

  1. 根据原因,可以在前端请求时,数据库如果未命中的情况下,为当前请求的数据,添加一个nulI值,添加到缓存中,以后再来访问,可以避免请求直接到达数据库,但
    是,对于null值不设置清理时机,就会导致数据一直堆积,内存变大,所以可以在添加缓存时,为缓存添加一个过期时间,避免数据堆积,造成内存占用较大
  2. 添加布隆过滤器,在前端请求时,可以先访问布隆过滤器,判断当前访问的数据,数据库中是否存在,之后在进行访问查询。

2. 缓存雪崩问题

背景: 1当前缓存中的数据同时大量失效,导致请求直接到达数据库,导致数据库压力变大 ,当前redis发生宕机时,请求直接到达数据库.
解决方案:第二种问题,我们可以对redis搭建集群进行管理,当redis宕机时,可以切换节点,避免大量的请求达到数据库。
第一种问题,针对数据库的key同时大量失效导致的问题,我们可以在添加数据到缓存的时候,在原有的基础上为缓存添加随机过期时间,解决问题。

3.缓存击穿问题

背景: 当一个高访问量的请求,过期时,同时有大量的请求访问数据库,导致数据库压力过大。
解决方案:

  1. 单体系统,可以设置同步锁,避免大量的请求访问到数据库,而是一个一个访问 这种解决方案:阻塞 分布式系统无法实现
  2. 分布式系统,利用redis的setnx命令,互斥锁,当我们访问的时候,进行获取锁,获取到的就去把数据添加并缓存并返回。这种解决方案:导致数据还是访问到数据库
  3. 最佳解决方案:缓存采用逻辑过期方案,在请求到达缓存时,判断数据是否过期,未过期直接使用,过期,获取互斥锁,获取成功,开启新线程直接去处理查询数据并添加缓存后,返回旧数
    据。获取失败,直接放回旧数据。 选择:当我们要求数据强一致性的要求时,可以选择分布式锁实现, 弱一致性数据要求,可以采用缓存逻辑过期+互斥锁

4 数据缓存一致性问题:

背景: 为了避免数据的请求压力过大,响应时间过长的问题,我们可以将数据添加到缓存中,当前查询时可以,判断缓存中是否存在,存在直接返回,不存在再去数据库查询,查询到数据后,添加
缓存中一份,后续查询使用,并返回数据给前端。可问题一会随之浮现,由于缓存中的数据与数据库的数据不一致,可能造成业务问题,所以要保证缓存中与数据库中的数据一致性。
解决方案: 我们可以手动添加缓存更新策略

  1. 当数据库数据更新时,直接更新缓存中的数据,这种方案,遇到前端做更新操作频繁的,查询业务少的请求,为此还需要去每次都去更新数据库,造成了时间浪费
  2. 当数据库数据更新时,直接删除缓存中的数据,这种方案,可以解决这种问题。但随之会出现一个线程安全问题:我们是先更新数据库数据,删除缓存,还是先删除缓存,在更新数据库数据,这两种方案都是有问题的,但是第二种方案出现问题的频率小,但是都能解决。
    • 先删除缓存,在更新数据库:当我们有一个线程,在查询时,这是插入了一个修改的操作,由于第一个请求刚查询完,准备数据并添加缓存之前,这是数据修改了,但是请求已经查询完毕,又把旧数据添加到了缓存,造成了数据不一致性。对于这中不一致性,我们可以为缓存添加过期时间,保证缓存最终一致,或者,在更新完毕后,再次进行缓存的删除,确保数据最新
    • 先更新数据库,在删除缓存,这种方案出现,问题发生概率小,出现的情况必须满足,在我们更新之前,刚好有一个key过期了,来查询完旧数据,我们这边就开始更新删除,而另外一个线程,在我们更新删除完之后,把数据直接添加到了缓存,出现了数据不一致性,这时我们可以为缓存也添加过期时间,保证数据最后一致,或者,在更新删除后,开启一个新的线程进行延时后再删除,避免这种问题,这种问题出现场景比较极端,比较少见,所以缓存更新都采用更新后在删除。

你可能感兴趣的:(缓存)