0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

聊聊缓存击穿的解决方法

马哥Linux运维 来源:博客园snail_lie 2024-10-23 13:54 次阅读

缓存击穿,Redis中的某个热点key不存在或者过期,但是此时有大量的用户访问该key。比如xxx直播间优惠券抢购、xxx商品活动,这时候大量用户会在某个时间点一同访问该热点事件。但是可能由于某种原因,redis的这个热点key没有设置,或者过期了,那么这时候大量高并发对于该key的请求就得不到redis的响应,那么就会将请求直接打在DB服务器上,造成DB突刺,CPU和内存瞬间被打满,最终导致服务崩溃。

本人所负责的业务就存在这样的场景,以直播间邀请榜单为例,顾名思义就是会查询该直播间实时的邀请人数,统计前30名邀请人数最多的用户展示在直播间里面,通过榜单去刺激C端用户的分享参与热情。下面一起分析下这个场景遇到的问题和解决方案。

问题1:
统计邀请榜单需要加载实时的,即我邀请一个人进来,假设在前30名,那我不得上榜吗?那问题来了,这种数据我是不是得实时去查数据库呢?

解决方案:这种业务,我们一般会设置一个短时间的缓存,比如30秒左右。也就是在缓存失效后,即30秒去查一次数据库,不然数据库肯定是顶不住的。

问题2:
我们常规的设置缓存的代码逻辑可能是下面这种。(代码片段错误处理等细节请自行处理,这是一段精简版的代码,主要介绍Redis的处理逻辑)

//step1:读缓存,存在则返回结果
ctx := context.Background()

rdb := redis.NewClient(&redis.Options{
Addr:     "localhost:6379",
Password: "123456",
DB:       0,
})

redisKey := "xxx_xxx_xxx" //邀请榜单数据的key

res, err := rdb.Get(ctx, redisKey).Result()
if err == nil {
return res
}

//step2:不存在缓存,读DB
//此处省略,查DB的数据,结果为res

//step3:设置缓存,并返回结果
args := redis.SetArgs{
TTL:  time.Second * 30,
Mode: "EX",
}
_, _ = rdb.SetArgs(ctx, redisKey, res, args).Result()

return res

这种代码逻辑在并发量小的情况下是没有任何问题的,事实上我平时写一些业务,基本上就把它当成一个“公式”来用,用的非常多。然而,在一些高并发的场景下,这种逻辑就会出现问题。试想一下这个场景:假如某个大直播(用户量巨大)是在晚上8点开播,那么8点一到,那个瞬间就会有大量的C端用户进入直播间,去调用后端的接口,假如此时接口的Redis缓存已经过期或者不存在,那么这一刻就会有大量的请求落到DB上,可想而知这一刻DB的压力是多么巨大(这谁顶得住啊)。这就是一个典型的缓存击穿的业务场景。
那么我们需要怎么做,才能让我们的服务抵抗住瞬时的请求洪峰呢?

解决方案:
解决缓存击穿的常见方法有几种:
1、设置该key永不过期,那么就不会存在缓存失效、过期等问题。但这种方法很明显不适合我这种场景,因为我上面提到过,我这个key值存的是邀请榜单的数据,是动态更新的,在直播中,这个榜单的数据是会变化的,所以只能设30秒的缓存时间。该方案行不通。

2、人工干预该key,比如写一个脚本去定时读DB数据,然后更新这个key,然后业务侧(对接前端的接口)只能通过读该key的缓存去获取结果数据,而不能直接读DB。这样也能解决问题,但是貌似维护成本有点高,而且业务侧不能读DB也很不灵活,你想下如果每个热点key都这样去设置维护,那估计会很烦吧。该方案也行不通。

3、使用互斥锁,即在缓存失效的时候,只有一个请求可以获取到互斥锁,然后去查DB,最后重建缓存。这种方案就能很好地解决缓存击穿这个问题,也是我在工作中用来应对缓存击穿问题的最常用的方案。下面是精简版代码:

//step1:读缓存,存在则返回结果
ctx := context.Background()

rdb := redis.NewClient(&redis.Options{
Addr:     "localhost:6379",
Password: "123456",
DB:       0,
})

redisKey := "xxx_xxx_xxx" //邀请榜单数据的key

res, err := rdb.Get(ctx, redisKey).Result()
if err == nil {
return res
}

//step2:不存在缓存,加互斥锁,读缓存
lockKey := "yyy_yyy_yyy" //互斥锁的key

argsLock := redis.SetArgs{
TTL:  time.Second * 3,
Mode: "NX", //不存在时才执行
}

_, err = rdb.SetArgs(ctx, lockKey, "1", argsLock).Result()
if err != nil { //获取互斥锁失败
for i := 0; i < 3; i++ { //重复三次去读缓存值
res, errRetry := rdb.Get(ctx, redisKey).Result()
if errRetry == nil { //重试读缓存成功,则返回结果
return res 
}
time.Sleep(10 * time.Millisecond) //这里睡眠时间根据业务来定,取的是另一个线程从读数据库到设置缓存成功的大概时间区间
}
return nil //如果循环三次,都读不到缓存,则返回空结果
}

//step3:获取互斥锁成功,则表明当前的线程/协程拥有查DB的权力
//此处省略,查DB的数据,结果为res

//step4:设置缓存,删除互斥锁,并返回结果
args := redis.SetArgs{
TTL:  time.Second * 30,
Mode: "EX",
}
_, _ = rdb.SetArgs(ctx, redisKey, res, args).Result()

rdb.Del(ctx, lockKey) //删除互斥锁

return res

以上就是个人在线上的一些项目面对缓存击穿问题,所做的一些处理方案了。当然这个方案也不是完美的,例如当获取到互斥锁的当前线程/协程,出现异常,导致设置缓存失败,那么其他线程/协程就重试3次可能都获取不到正常结果,最后返回了一个空结果给前端。感兴趣的朋友可以想想这个方案还有什么问题,然后能怎么优化,欢迎指出

一个人可以被毁灭,但不可以被打败。

链接:https://www.cnblogs.com/lmz-blogs/p/18173813

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 服务器
    +关注

    关注

    12

    文章

    9046

    浏览量

    85237
  • 缓存
    +关注

    关注

    1

    文章

    233

    浏览量

    26650
  • Redis
    +关注

    关注

    0

    文章

    371

    浏览量

    10858

原文标题:go高并发之路——缓存击穿

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    鸿蒙OpenHarmony:【常见编译问题和解决方法

    常见编译问题和解决方法
    的头像 发表于 05-11 16:09 2048次阅读

    阻容降压 刚一上电 稳压管就击穿了,然后限流的线绕电阻也烧了,请问大佬这是怎么回事,有什么解决方法吗。

    `阻容降压 刚一上电 稳压管ZD1和2就击穿了,然后限流的线绕电阻R20也烧了,请问大佬这是怎么回事,有什么解决方法吗。`
    发表于 04-26 16:38

    聊聊环形缓存在单片机程序中的使用

    片头因为环形缓存在单片机程序中的使用是非常有效的,非常有用的,关于这个话题在此专门开一文章来聊聊这个话题。环形缓存的用途主要是来缓存数据,而需要缓存
    发表于 12-06 08:29

    短波通信盲区现象解决方法介绍

    短波通信盲区现象解决方法介绍短波通信盲区现象解决方法介绍短波通信盲区现象解决方法介绍
    发表于 11-10 17:13 5次下载

    DXP2004 warning报警及解决方法

    DXP2004 warning报警及解决方法
    发表于 12-26 15:58 0次下载

    sdwebimage清除缓存方法

    清除通过SDWebImage进行的缓存;Sdwebimage手动清除缓存方法;iOS SDWebImage清空缓存方法.
    发表于 11-09 14:38 3600次阅读
    sdwebimage清除<b class='flag-5'>缓存</b><b class='flag-5'>方法</b>

    电容击穿是开路还是短路_电容击穿原因是什么

    本文开始阐述了电容击穿的概念和电容器被击穿的条件,其次分析了电容击穿后是开路还是短路,最后介绍了电容击穿的原因以及避免介质击穿
    发表于 03-27 18:21 6w次阅读

    内存安装和使用常见问题的解决方法资料分析

    自锐龙平台发布以来,AMD CPU凭借超高的性价比迅速崛起,如今整个市场已成为AMD和Intel平分秋色“五五开”的局面。但是尽管AMD锐龙平台来势汹汹,其内存控制器一直处于有待完善的水平。所以今天小编就藉此机会简单聊聊关于内存常见问题的解决方法
    的头像 发表于 12-15 11:11 4373次阅读

    如何设计一个缓存系统?

    设计一个缓存系统,不得不要考虑的问题就是:缓存穿透、缓存击穿与失效时的雪崩效应。 缓存穿透 缓存
    的头像 发表于 02-08 11:40 2901次阅读

    聊聊缓存数据库一致性

    在云服务中,缓存是极其重要的一点。所谓缓存,其实是一个高速数据存储层。当缓存存在后,日后再次请求该数据就会直接访问缓存,提升数据访问的速度。
    的头像 发表于 01-30 17:41 748次阅读

    如何在SpringBoot中解决Redis的缓存穿透等问题

    今天给大家介绍一下如何在SpringBoot中解决Redis的缓存穿透、缓存击穿缓存雪崩的问题。
    的头像 发表于 04-28 11:35 714次阅读

    聊聊分页列表缓存设计

    这是最简单易懂的方案,我们按照不同的分页条件查询出结果后,直接缓存分页结果 。
    的头像 发表于 06-06 18:25 711次阅读
    <b class='flag-5'>聊聊</b>分页列表<b class='flag-5'>缓存</b>设计

    聊聊本地缓存和分布式缓存

    本地缓存 :应用中的缓存组件,缓存组件和应用在同一进程中,缓存的读写非常快,没有网络开销。但各应用或集群的各节点都需要维护自己的单独缓存,无
    发表于 06-11 15:12 819次阅读
    <b class='flag-5'>聊聊</b>本地<b class='flag-5'>缓存</b>和分布式<b class='flag-5'>缓存</b>

    Redis缓存预热+缓存雪崩+缓存击穿+缓存穿透要点简析

    缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统。
    的头像 发表于 12-25 09:41 870次阅读
    Redis<b class='flag-5'>缓存</b>预热+<b class='flag-5'>缓存</b>雪崩+<b class='flag-5'>缓存</b><b class='flag-5'>击穿</b>+<b class='flag-5'>缓存</b>穿透要点简析

    MOS管击穿原理分析、原因及解决方法

    MOS管(金属-氧化物-半导体场效应管)是一种常用的电子元件,在电路中起着开关、放大等重要作用。然而,在某些情况下,MOS管可能会发生击穿现象,导致其失效。击穿原理主要涉及电场强度、电荷积累、热量等因素。
    的头像 发表于 10-09 11:54 3161次阅读