应对缓存击穿的解决方法

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

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

二.什么是缓存击穿

三.缓存击穿的解决办法

方案一

后台刷新

后台定义一个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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 猴子吃桃

    猴子吃桃猴子吃桃

    2022年4月24日
    47
  • python动图爱心表白_用python画动态图

    python动图爱心表白_用python画动态图初级画心学Python,感觉你们的都好复杂,那我来个简单的,我是直接把心形看作是一个正方形+两个半圆:于是这就很简单了,十行代码解决:importturtleastt.pensi…

    2022年10月7日
    0
  • Perl threads 摘要

    Perl threads 摘要最近又写了一个多线程的小工具 对一些多线程的使用有了进一步的心得 Perl 创建线程有两种方式 正常通过 threads create 创建线程 用 async nbsp 创建一个调用匿名过程的线程 具体参考 perldocthrea 线程共享变量需要使用 threads shared 共享变量只能存储 scalar 共享变量的引用 如果存储 ListHash 的引用需使用 shared clo

    2025年6月30日
    3
  • linux下面的解压缩文件的命令[通俗易懂]

    linux下面的解压缩文件的命令[通俗易懂]尝试去好好用linux、新手起步。这边只会提到我用过的、其他相关的以后我用到了我会补充的。如果有错欢迎指正注:1、c-创建-create  2、v-复杂输出 3、f-文件-file4、x-解压-extract5、z-gz格式  66666、真不会用语法的就使用man…例如mantar他就会给你现实tar的一些参数操作…

    2022年5月24日
    45
  • setfacl 命令[通俗易懂]

    setfacl 命令[通俗易懂]setfacl命令是用来在命令行里设置ACL(访问控制列表)。在命令行里,一系列的命令跟随以一系列的文件名。选项-b,–remove-all:删除所有扩展的acl规则,基本的acl规则(所有者,群组,其他)将被保留。-k,–remove-default:删除缺省的acl规则。如果没有缺省规则,将不提示。-n,–no-mask:不要重新计算有效权限。setfacl…

    2022年6月16日
    56
  • 又一个新的开始「建议收藏」

    又有一段时间没有进行整理和总结输出了,其实最近也没有闲着,也是一直在看书学习状态,看Java并发编程相关的知识,之前买了《Java并发编程的艺术》,去年看了一遍。最近又买了《Java并发编程实战》,两本书都挺好的,《Java并发编程的艺术》这本看了一遍,最近在看其中的一些章节,又有新的一些体会,新的认识。很多时候看了一遍的书,就不会在读第二遍,但是有些书适合读两遍以上,要不然根本无法理解书中的精…

    2022年2月27日
    41

发表回复

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

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