热点数据多级缓存方案实现(进行中)


热点数据多级缓存方案实现

集成CountMinSketch过滤器+本地缓存caffeine+redis缓存+数据库的多级缓存方案

涉及技术点:

  1. caffeine本地缓存
  2. redis:lua脚本、redis事务的原子性
  3. CountMinSketch算法
  4. 设计思想:计算向数据端迁移

1,背景概述

我们系统在使用过程中,并非所有的数据都是时刻活跃的状态,一般情况下只有少部分的活跃数据占据大量的请求。所以,我们在将其他非活跃数据也存放于redis缓存或者本地缓存时,是非常浪费内存资源的一种方式(钱多、机器多的可以无视这个)。

所以为了解决上述浪费内存资源的情况,我们一般将热点数据(当前时间段访问频繁的数据)放入缓存中从而用于加快数据的响应速度,举一个生活中的实例:我们平时看视频网站中的视频,如果是当前热播的视频,我们点开后加载速度相对于那些冷门的视频速度快很多。这个就是因为这些视频网站,将热门视频数据也是放置在缓存中,冷门数据还是存储在磁盘中(节省费用)。

当前热点数据缓存主要有如下几个解决方案:

  1. 使用本地缓存,例如caffeine、guava、或者自定义缓存。
    • 它们直接在单节点服务中使用堆内存进行热点数据的缓存;
    • 优点:响应及时、读并发响应及时;
    • 缺点:
      • ①消耗堆内存;
      • ②如果缓存中不存在需要查询数据库(缓存穿透),如果是多节点服务,每个节点都需要重复查询数据库,造成数据库查询并发量高
      • 已删除数据不容易进行多节点同步
  2. 使用redis缓存
    • 直接使用redis中间件的内存操作功能实现热点数据缓存
    • 优点:可以响应多节点请求与数据一致性同步
    • 缺点:
      • 相较于本地缓存来说,此方案需要额外的网络通信耗时
      • ②由于redis的单线程数据操作,当有大key等耗时指令时,其他请求需要进行排序等待;
      • ③需要依赖中间件;
      • 并发相对于本地缓存来说要低一些
      • ⑤如果缓存中不存在需要查询数据库(缓存穿透);
      • 热点数据的缓存策略算法没有caffeine等缓存框架优秀(redis只有单纯的LRU、LFU、TTL等简单驱逐策略),热点数据命中率低;

另外,在进行数据缓存时,我们还经常遇到缓存穿透的问题(查询不存在或者冷门的数据,这个操作会查询至数据库,并发量如果很高容易导致服务崩溃),一般情况下我们可以使用布隆过滤器来解决该问题,但是布隆过滤器也有自身的问题(只能新增数据,不能删除历史数据)。

因此总结上述常用缓存方案存在的问题:

  1. 两种方案都存在的缓存穿透问题,同时使用布隆过滤器解决缓存穿透又无法删除历史数据;
  2. 本地缓存与redis缓存各有优缺点;

这里为了解决上述的两个问题提出了如下的方案【集成CountMinSketch过滤器+本地缓存caffeine+redis缓存+数据库的多级缓存方案】:

  1. 利用Count-Min Sketch算法解决缓存穿透的问题:
    • ①与布隆过滤器一样,解决查询客观不存在数据,直接查询数据库的问题;
    • ②优化布隆过滤器中,不能删除历史数据的缺陷;
  2. 利用多级缓存解决之前2种缓存方案存在的缺陷:
    • ①多节点查询客观存在的数据,如果本地缓存存在该数据,现在是直接查询redis缓存,由redis缓存只查询一次数据库;
    • ②由于存在redis用于存储CountMinSketch过滤器的key是否存在的数据信息,并且该过滤器中的历史数据可以被删除,所以保证了每个节点的本地缓存数据一致性;
    • ③现在使用本地缓存消除了之前redis缓存需要额外网络通信的问题,并且继承了本地缓存的缓存算法优势,保证了缓存命中率;

image-20220417162454494

同时,也需要额外说明该实现的方案的缺点:

  1. 实现相对复杂,逻辑依赖redis和数据库(后续可以通过AOP切面代理模式,提取公共部分逻辑,生成方法注解)
  2. 当前不支持数据的修改,只支持删除和新增(后续可以通过RPC调用的方式,单节点修改同步数据至其他节点,同时这些节点信息可以直接使用redis存储或者 )

2,技术原理

本处的多级缓存方案的结构图如下:

image-20220417151719520

逻辑流程图:

image-20220417153347873

流程描述:

  1. 外部查询请求进入,先查询CountMinSketch过滤器

    • 如果过滤器中,不存在该数据,直接返回null,然后结束流程
    • 如果过滤器中,存在该数据,直接进入第2步
  2. 查询caffeine本地缓存

    • 如果本地缓存中,存在该数据,直接返回数据,然后结束流程
    • 如果本地缓存中,不存在该数据,则进行第3步进行查询,
      • 查询结果为null,则直接返回给调用方
      • 查询结果不为null,则更新至本地缓存后,返回结果数据,然后结束流程
  3. 查询redis缓存

    • 如果redis缓存中,存在该数据,重置过期时间,返回该数据
    • 如果redis缓存中,不存在该数据,则进入第4步进行查询,
      • 查询结果为null,直接返回给调用方
      • 查询结果不为null,则更新至redis缓存后,返回给数据调用方
  4. 查询db数据库

    • 如果db数据库中,存在该数据,则直接返回给调用方
    • 如果db数据库中,不存在该数据,则将该key值对应于CountMinSketch过滤器中数据自减1,然后将null值返回给调用方

3,代码实现

4,注意事项

5,疑问解答

  1. 之前面试的时候,有面试官认为查询CountMinSketch过滤器的时候,都已经查询过redis,那么为什么不在这次查询的时候直接把数据返回?反而搞这么复杂的逻辑呢?
    • ①首先,我们已经简述过本地缓存caffeine相对于redis缓存的优势,这个是我们为什么尽量使用caffeine本地缓存,而将redis缓存作为辅助的原因。
      • caffeine的缓存算法更优,命中率更高,并发量也更高(算法优势
      • 本地缓存查询数据直接使用的堆内存数据,无需额外网络通信消耗,响应更快(类比于计算机CPU的三级缓存机制)
    • ②其次,我们查询redis中的CountMinSketch过滤器时是将运算向数据迁移,最终只需要获取redis返回的该key值是否存在的布尔值即可,网络消耗很少(后续还可以进一步在redis中自定义查询函数,更加精简查询命令传输)

参考链接

原创文章,作者:3628473679,如若转载,请注明出处:https://blog.ytso.com/245373.html

(0)
上一篇 2022年4月18日
下一篇 2022年4月18日

相关推荐

发表回复

登录后才能评论