缓存击穿,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次可能都获取不到正常结果,最后返回了一个空结果给前端。感兴趣的朋友可以想想这个方案还有什么问题,然后能怎么优化,欢迎指出

go高并发之路——缓存击穿的更多相关文章

  1. redis 缓存击穿 看一篇成高手系列3

    什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义.如果 ...

  2. python开发之路:python数据类型(老王版)

    python开发之路:python数据类型 你辞职当了某类似微博的社交网站的底层python开发主管,官还算高. 一次老板让你编写一个登陆的程序.咔嚓,编出来了.执行一看,我的妈,报错? 这次你又让媳 ...

  3. redis缓存雪崩,缓存穿透,缓存击穿的解决方法

    一.缓存雪崩 缓存雪崩表示在某一时间段,缓存集中失效,导致请求全部走数据库,有可能搞垮数据库,使整个服务瘫痪. 使缓存集中失效的原因: 1.redis服务器挂掉了. 2.对缓存数据设置了相同的过期时间 ...

  4. 缓存击穿、缓存失效及热点key的解决方案

    分布式缓存是网站服务端经常用到的一种技术,在读多写少的业务场景中,通过使用缓存可以有效地支撑高并发的访问量,对后端的数据库等数据源做到很好地保护.现在市面上有很多分布式缓存,比如Redis.Memca ...

  5. Redis 缓存穿透,缓存击穿,缓存雪崩的解决方案分析

    设计一个缓存系统,不得不要考虑的问题就是:缓存穿透.缓存击穿与失效时的雪崩效应. 一.什么样的数据适合缓存? 分析一个数据是否适合缓存,我们要从访问频率.读写比例.数据一致性等要求去分析.  二.什么 ...

  6. 【Redis】- 缓存击穿

    什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义.如果 ...

  7. 使用BloomFilter布隆过滤器解决缓存击穿、垃圾邮件识别、集合判重

    Bloom Filter是一个占用空间很小.效率很高的随机数据结构,它由一个bit数组和一组Hash算法构成.可用于判断一个元素是否在一个集合中,查询效率很高(1-N,最优能逼近于1). 在很多场景下 ...

  8. 什么是redis缓存穿透, 缓存雪崩, 缓存击穿

    什么是redis? redis是一个非关系型数据库,相对于其他数据库而言,它的查询速度极快,且能承受的瞬时并发量非常的高.所以常常被用来存放网站的缓存,以减少主要数据库(如mysql)的服务器压力. ...

  9. Redis缓存雪崩、缓存穿透、缓存击穿、缓存降级、缓存预热、缓存更新

    Redis缓存能够有效地加速应用的读写速度,就DB来说,Redis成绩已经很惊人了,且不说memcachedb和Tokyo Cabinet之流,就说原版的memcached,速度似乎也只能达到这个级别 ...

  10. 【原创】分布式之缓存击穿 【原创】自己动手实现静态资源服务器 【原创】自己动手实现JDK动态代理

    [原创]分布式之缓存击穿   什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查 ...

随机推荐

  1. 【FAQ】关于分析服务错误获取所选日期前一天事件数据的解决方法

    开发者通过华为分析服务下载所需的事件数据,这些数据可以导入到开发者自有的分析系统中,用于构建自定义报告或生成受众群体的个性化分析等,从而帮助制定切实有效的营销活动.数据导出支持按照用户属性和导出事件作 ...

  2. html-testRunner中文乱码

    如下图,使用 html-testRunner 这个库生成测试报告后,出现乱码 因为  HTML文件已经写了  文件编码是  utf-8 所以 我怀疑可能是 html-testRunner 这个库文件中 ...

  3. openGauss每日一练第6天

    学习地址 https://www.modb.pro/course/133 学习目标 学习 openGauss 创建模式.修改模式属性和删除模式 模式是一组数据库对象的集合,主要用于控制对数据库对象的访 ...

  4. HarmonyOS:使用MindSpore Lite引擎进行模型推理

      场景介绍 MindSpore Lite是一款AI引擎,它提供了面向不同硬件设备AI模型推理的功能,目前已经在图像分类.目标识别.人脸识别.文字识别等应用中广泛使用. 本文介绍使用MindSpore ...

  5. ​CentOS防火墙操作命令 ​

    CentOS防火墙操作命令 1.查看防火墙服务状态 systemctl status firewalld.service 或者查看防火墙的状态: 1 firewall-cmd --state 2.开启 ...

  6. 实训篇-Html-表格练习1

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  7. MRBS(Meeting Room Booking System)开源的会议室预订系统搭建使用

    前一家公司所有的办公系统都是自己开发的,包括排班.工单.会议室预定等等,很方便. 目前所在的公司,每周部门例会找行政预订了会议室,但多次去都被人占了,很烦,于是网上找了一个评价不错的系统,python ...

  8. JavaServlet类

    "感谢您阅读本篇博客!如果您觉得本文对您有所帮助或启发,请不吝点赞和分享给更多的朋友.您的支持是我持续创作的动力,也欢迎留言交流,让我们一起探讨技术,共同成长!谢谢!" 介绍Ser ...

  9. 百万TPS高吞吐、秒级低延迟,阿里​搜索离线平台如何实现?

    导读:阿里主搜(淘宝天猫搜索)是搜索离线平台非常重要的一个业务,具有数据量大.一对多的表很多.源表的总数多和热点数据等特性.对于将主搜这种逻辑复杂的大数据量应用迁移到搜索离线平台总是不缺少性能的挑战, ...

  10. 探秘RocketMQ源码——Series1:Producer视角看事务消息

    简介: 探秘RocketMQ源码--Series1:Producer视角看事务消息 1. 前言 Apache RocketMQ作为广为人知的开源消息中间件,诞生于阿里巴巴,于2016年捐赠给了Apac ...