【Java面试】一、Redis篇(上)

【Java面试】一、Redis篇(上)

码农世界 2024-06-11 后端 107 次浏览 0个评论

文章目录

  • 0、准备
  • 1、缓存穿透:不存在的key
  • 2、缓存击穿:热点key过期
  • 3、缓存雪崩:大批key同时过期
  • 4、双写一致性
    • 4.1 要求高一致性
    • 4.2 允许一定的一致延迟
    • 5、面试

      0、准备

      Redis相关概览:

      【Java面试】一、Redis篇(上)

      以简历上所列的项目为切入点,展开Redis相关的问题:

      【Java面试】一、Redis篇(上)

      1、缓存穿透:不存在的key

      Client ⇒ Redis ⇒ MySQL,缓存穿透,即中间的Redis形同虚设,请求每次过来都查库。一般是网站被攻击时,疯狂造不存在数据,然后发起请求,冲击数据库,消耗数据库连接池资源,直到服务不可用。

      【Java面试】一、Redis篇(上)

      解决方案一:缓存空数据

      【Java面试】一、Redis篇(上)

      查到的结果为空也写进Redis。实现简单但消耗内存,可能会发生不一致的问题(即库里有数据了,但缓存里依旧是null,导致查不到最新数据)

      解决方案二:布隆过滤器

      缓存预热的时候,往布隆过滤器中添加数据。后面请求过来时,先经过布隆过滤器,判断ID不存在的话,直接返回,都走不到Redis。

      【Java面试】一、Redis篇(上)

      如此,内存占用少,但实现复杂,且存在误判(即有的ID不存在,但布隆过滤器会判断为存在)

      【Java面试】一、Redis篇(上)

      bitmap(位图):一个bit数组,每个单元非0即1。预热ID的时候,用多个hash函数获取该ID的hash值,并把bitmap对应位置改为1。后面查数据是否存在时,就用相同的hash函数获取hash值,查看对应的位置是否都为1。由此,布隆过滤器实现了检索一个元素是否在一个集合中。

      【Java面试】一、Redis篇(上)

      如上,id1和id2点亮了1、3、7和9、12、15的位置为1,id3虽然不存在,但其hash值所在位置都为1,判定为是存在的ID,即误判。数组越大,误判率越低。

      2、缓存击穿:热点key过期

      给一个热点key设置了过期时间,当这个key过期的时候,恰好有大量请求查这个key,这些请求自然都走到了数据库,可能导致DB服务不可用。如下图,比如查询一次MySQL再写到Redis需要50ms,热点数据过期后,这50ms内的大量请求都会冲到数据库

      【Java面试】一、Redis篇(上)

      解决方案一:添加互斥锁

      【Java面试】一、Redis篇(上)

      缓存未命中,查库前,获取一个互斥锁,获取不到则一直重试,如此,后面的线程走不到查库这一步,直到线程1完成查库+写入Redis,其余线程就会命中缓存。如此,保护了数据库服务且保证了数据强一致性,但性能不好

      解决方案二:逻辑过期

      Redis层面,不设置过期时间,但业务层面加一个过期时间,如

      keyvalue
      001{“id”:“123”,“title”:“标题1”,"expire":1716368322}

      【Java面试】一、Redis篇(上)

      即明天16:58的时候,这条数据逻辑过期,但你在Redis中还能查到,不过是查到了过期数据,不是最新的。具体流程:

      【Java面试】一、Redis篇(上)

      线程1过来查缓存,发现当前时间超过了缓存的逻辑过期时间,于是线程1获取一个互斥锁,再开启一个线程2去查库 + 更新缓存,自己则先把过期数据返回。此过程中,即使有线程3进来,并发现了已超过逻辑过期时间,它也不会重复去查库重建缓存,因为它获取不到互斥锁,此时线程3会先返回过期数据。 因此,这种方式性能好,但数据一致性不保证。

      3、缓存雪崩:大批key同时过期

      同一时间段,大量缓存的key过期,或者Redis服务宕机,导致大量请求冲到数据库

      【Java面试】一、Redis篇(上)

      解决思路是:给不同的key添加随机的TTL(过期时间),如此,大量的key不会同时过期。至于Redis宕机,则可以用Redis哨兵模式或者集群模式、给业务添加多级缓存等方式解决。

      最后,降级限流,可用于解决缓存穿透、击穿、雪崩的解决。即服务请求失败次数到一定阈值,直接走降级策略(比如不查库,直接返回空)

      //Tips:
      穿透无中生有key,布隆过滤null隔离
      缓存击穿过期key,锁与非期解难题
      雪崩大量过期key,过期时间要随机
      面试必考三兄弟,可用限流来保底
      

      4、双写一致性

      相关问题:MySQL和Redis的数据如何进行同步。两种追求:

      • 一致性高
      • 允许一定的一致延迟

        4.1 要求高一致性

        【Java面试】一、Redis篇(上)

        读操作:命中直接返回。未命中缓存则 查库 + 写入缓存

        写操作:数据库更新时,进行延时双删

        【Java面试】一、Redis篇(上)

        有数据库的写操作时,为了数据一致性,如果不双删,只删一次缓存 + 改库,则实现方式可以是:

        • 先删缓存,再改库
        • 先改库,再删缓存
          1)若先删缓存,再改库:
          

          理想状态为:线程1删缓存,再更新数据库,后面请求过来(对应线程2),先查缓存,未命中,去查库 + 写入缓存,一切正常

          【Java面试】一、Redis篇(上)

          再看非理想状况:线程1删除缓存后挂起,此时缓存为null,库中为10。然后新的请求进来(对应线程2),CPU切到线程2执行,查缓存未命中,线程2去查库 + 写入缓存,线程2执行结束,此时缓存为10,库中为10。最后线程1解除挂起,继续执行,线程1去更新了数据库,此时缓存为10,库中为20 ,数据不一致!

          【Java面试】一、Redis篇(上)

          总之,高并发下,线程交替执行,这样实现会出现数据不一致问题。

          2)若先改库,再删缓存
          

          理想状态为:线程2更新数据库,再删缓存,此时缓存为null,库中为20。后面请求过来(对应线程1),先查缓存,未命中,去查库 + 写入缓存,一切正常

          【Java面试】一、Redis篇(上)

          非理想状态下:比如一开始缓存过期,即缓存中为null,库中为10,线程1进来未命中缓存,查库得到10,然后挂起。请求2进来(对应线程2),其进行了update,改库,并删缓存,此时,库中为20,缓存中为null。线程2执行结束,线程1解除挂起继续执行,将查到的数据,写入缓存。此时缓存中为10,库中为20,数据不一致。

          【Java面试】一、Redis篇(上)

          因此,不管先删缓存还是先改库,都可能出现数据不一致
          

          ==> 延时双删:先删缓存,数据库更新完后,再删一次缓存。至于为什么要延时后第二次删,是因为如果数据库是主从模式,读写分离,那就需要等主节点把数据同步到从节点。但这个延时的大小不好控制,还是可能出现脏数据。想强一致,可用分布式锁。

          【Java面试】一、Redis篇(上)

          一个线程在更新数据前,加锁,update库 + 删缓存后,释放锁。期间,其余线程不能读写。以上可优化为:读数据的时候,添加共享锁,读读共享,读写互斥。写数据的时候,加排他锁,阻塞其他线程的读和写。如此,实现数据强一致性,但性能低。

          4.2 允许一定的一致延迟

          异步通知,保证数据的最终一致性。业务服务更新库后,发消息到MQ,缓存服务监听MQ,去更新缓存

          【Java面试】一、Redis篇(上)

          以上实现对业务代码有一定的侵入性,需要添加发消息的代码。可把MQ替换为Canal:

          【Java面试】一、Redis篇(上)

          阿里的Canal基于MySQL的主从同步实现。数据库一旦发生改变,二进制的binlog记录DDL语句和DML语句,Canal伪装成MySQL的一个从节点,监听读取MySQL的binlog去更新缓存。此方式不用改业务代码,无侵入性。

          最后,如果不要求实时性和强一致性,如热点文章数据,可用异步方案同步数据。如果要求数据强一致性,如抢券的库存,则采用redisson提供的读写锁保证数据同步。

          5、面试

          【Java面试】一、Redis篇(上)

          【Java面试】一、Redis篇(上)

          【Java面试】一、Redis篇(上)

转载请注明来自码农世界,本文标题:《【Java面试】一、Redis篇(上)》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,107人围观)参与讨论

还没有评论,来说两句吧...

Top