应对缓存击穿的解决方法

应对缓存击穿的解决方法一 什么样的数据适合缓存 分析一个数据是否适合缓存 我们要从访问频率 读写比例 数据一致性等要求去分析 二 什么是缓存击穿在高并发下 多线程同时查询同一个资源 如果缓存中没有这个资源 那么这些线程都会去数据库查找 对数据库造成极大压力 缓存失去存在的意义 打个比方 数据库是人 缓存是防弹衣 子弹是线程 本来防弹衣是防止子弹打到人身上的 但是当防弹衣里面没有防弹的物质时 子弹就

一.什么样的数据适合缓存?

二.什么是缓存击穿

三.缓存击穿的解决办法

方案一

后台刷新

后台定义一个job(定时任务)专门主动更新缓存数据.比如,一个缓存中的数据过期时间是30分钟,那么job每隔29分钟定时刷新数据(将从数据库中查到的数据更新到缓存中).

  • 这种方案比较容易理解,但会增加系统复杂度。比较适合那些 key 相对固定,cache 粒度较大的业务,key 比较分散的则不太适合,实现起来也比较复杂。

方案二

检查更新

将缓存key的过期时间(绝对时间)一起保存到缓存中(可以拼接,可以添加新字段,可以采用单独的key保存..不管用什么方式,只要两者建立好关联关系就行).在每次执行get操作后,都将get出来的缓存过期时间与当前系统时间做一个对比,如果缓存过期时间-当前系统时间<=1分钟(自定义的一个值),则主动更新缓存.这样就能保证缓存中的数据始终是最新的(和方案一一样,让数据不过期.)

  • 这种方案在特殊情况下也会有问题。假设缓存过期时间是12:00,而 11:59
    到 12:00这 1 分钟时间里恰好没有 get 请求过来,又恰好请求都在 11:30 分的时
    候高并发过来,那就悲剧了。这种情况比较极端,但并不是没有可能。因为“高
    并发”也可能是阶段性在某个时间点爆发。


方案三

分级缓存

采用 L1 (一级缓存)和 L2(二级缓存) 缓存方式,L1 缓存失效时间短,L2 缓存失效时间长。 请求优先从 L1 缓存获取数据,如果 L1缓存未命中则加锁,只有 1 个线程获取到锁,这个线程再从数据库中读取数据并将数据再更新到到 L1 缓存和 L2 缓存中,而其他线程依旧从 L2 缓存获取数据并返回。

  • 这种方式,主要是通过避免缓存同时失效并结合锁机制实现。所以,当数据更
    新时,只能淘汰 L1 缓存,不能同时将 L1 和 L2 中的缓存同时淘汰。L2 缓存中
    可能会存在脏数据,需要业务能够容忍这种短时间的不一致。而且,这种方案
    可能会造成额外的缓存空间浪费。


方案四

加锁

方法1
 // 方法1: public synchronized List<String> getData01() { List<String> result = new ArrayList<String>(); // 从缓存读取数据 result = getDataFromCache(); if (result.isEmpty()) { // 从数据库查询数据 result = getDataFromDB(); // 将查询到的数据写入缓存 setDataToCache(result); } return result; }
  • 这种方式确实能够防止缓存失效时高并发到数据库,但是缓存没有失效的时候,在从缓存中拿数据时需要排队取锁,这必然会大大的降低了系统的吞吐量.
方法2
// 方法2: static Object lock = new Object(); public List<String> getData02() { List<String> result = new ArrayList<String>(); // 从缓存读取数据 result = getDataFromCache(); if (result.isEmpty()) { synchronized (lock) { // 从数据库查询数据 result = getDataFromDB(); // 将查询到的数据写入缓存 setDataToCache(result); } } return result; }
  • 这个方法在缓存命中的时候,系统的吞吐量不会受影响,但是当缓存失效时,请求还是会打到数据库,只不过不是高并发而是阻塞而已.但是,这样会造成用户体验不佳,并且还给数据库带来额外压力.
方法3
//方法3 public List<String> getData03() { List<String> result = new ArrayList<String>(); // 从缓存读取数据 result = getDataFromCache(); if (result.isEmpty()) { synchronized (lock) { //双重判断,第二个以及之后的请求不必去找数据库,直接命中缓存 // 查询缓存 result = getDataFromCache(); if (result.isEmpty()) { // 从数据库查询数据 result = getDataFromDB(); // 将查询到的数据写入缓存 setDataToCache(result); } } } return result; }

双重判断虽然能够阻止高并发请求打到数据库,但是第二个以及之后的请求在命中缓存时,还是排队进行的.比如,当30个请求一起并发过来,在双重判断时,第一个请求去数据库查询并更新缓存数据,剩下的29个请求则是依次排队取缓存中取数据.请求排在后面的用户的体验会不爽.

方法4
static Lock reenLock = new ReentrantLock(); public List<String> getData04() throws InterruptedException { List<String> result = new ArrayList<String>(); // 从缓存读取数据 result = getDataFromCache(); if (result.isEmpty()) { if (reenLock.tryLock()) { try { System.out.println("我拿到锁了,从DB获取数据库后写入缓存"); // 从数据库查询数据 result = getDataFromDB(); // 将查询到的数据写入缓存 setDataToCache(result); } finally { reenLock.unlock();// 释放锁 } } else { result = getDataFromCache();// 先查一下缓存 if (result.isEmpty()) { System.out.println("我没拿到锁,缓存也没数据,先小憩一下"); Thread.sleep(100);// 小憩一会儿 return getData04();// 重试 } } } return result; }
  • 最后使用互斥锁的方式来实现,可以有效避免前面几种问题.

当然,在实际分布式场景中,我们还可以使用 redis、tair、zookeeper 等提供的分布式锁来实现.但是,如果我们的并发量如果只有几千的话,何必杀鸡焉用牛刀呢?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/232398.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Android ArcGIS基础使用教程(10.2.8)[通俗易懂]

    Android ArcGIS基础使用教程(10.2.8)[通俗易懂]教程为全英文版,介者勿下。下载链接:https://pan.baidu.com/s/1yK-0ylaH6AHq4eV6XA6BHA提取码:7f7s

    2022年6月17日
    31
  • pycharm代码特效插件_pycharm插件在哪

    pycharm代码特效插件_pycharm插件在哪相信对于不少的Python程序员们都是用Pycharm作为开发时候的IDE来使用的,今天小编来分享几个好用到爆的Pycharm插件,在安装上之后,你的编程效率、工作效率都能够得到极大地提升…

    2022年8月27日
    2
  • 简述Python特点_python优缺点

    简述Python特点_python优缺点python特点1.软件质量(特色)在很大程度上,python更注重可读性、一致性和软件质量,python的设计致力于可读性,带来了比其他语言更优秀的可重用性和可维护性,python秉承了一种独特的简洁和高可读性的语法,以及一种高度一致的编程序模式。2.提高开发者效率(特色)相对于C、C++、Java等编辑/静态类型语言,python的开发效率提升了3-5倍,也就是说代码量是其他…

    2022年9月15日
    3
  • wireshark解析tcp报文的内容_wireshark解析vxlan

    wireshark解析tcp报文的内容_wireshark解析vxlan在TCP层,有个FLAGS字段,这个字段有以下几个标识:SYN,FIN,ACK,PSH,RST,URG.其中,对于我们日常的分析有用的就是前面的五个字段。它们的含义是:SYN表示建立连接,FIN表示关闭连接,ACK表示响应,PSH表示有DATA数据传输,RST表示连接重置。其中,ACK是可能与SYN,FIN等同时使用的,比如SYN和ACK可能同时为1,它表示的就是建立连…

    2022年9月30日
    5
  • Spring Boot 中使用@KafkaListener并发批量接收消息[通俗易懂]

    Spring Boot 中使用@KafkaListener并发批量接收消息[通俗易懂]kakfa是我们在项目开发中经常使用的消息中间件。由于它的写性能非常高,因此,经常会碰到Kafka消息队列拥堵的情况。碰到这种情况时,有不能直接清理整改消息队列,因为还有别的服务正在使用该队列。因此只能额外启动一个相同名称的consumer-group来加快消息消费(经测试,如果该topic只有一个分区,实际上再启动一个新的消费者作用不到)。具体代码在这里,欢迎加星号,fork。官方文档……

    2022年10月15日
    2
  • vsftp简介[通俗易懂]

    vsftp简介[通俗易懂]一、简介FTP(文件传输协议)全称是:VerySecureFTPServer。Vsftpd是linux类操作系统上运行的ftp服务器软件。vsftp提供三种登陆方式:1.匿名登录2.本地用户登录3.虚拟用户登录vsftpd的特点:1.较高的安全性需求2.带宽的限制3.创建支持虚拟用户4.支持IPV65.中等偏上的性能6.可分…

    2022年9月24日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号