go高并发之路——缓存击穿
缓存击穿,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高并发之路——缓存击穿的更多相关文章
- redis 缓存击穿 看一篇成高手系列3
什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义.如果 ...
- python开发之路:python数据类型(老王版)
python开发之路:python数据类型 你辞职当了某类似微博的社交网站的底层python开发主管,官还算高. 一次老板让你编写一个登陆的程序.咔嚓,编出来了.执行一看,我的妈,报错? 这次你又让媳 ...
- redis缓存雪崩,缓存穿透,缓存击穿的解决方法
一.缓存雪崩 缓存雪崩表示在某一时间段,缓存集中失效,导致请求全部走数据库,有可能搞垮数据库,使整个服务瘫痪. 使缓存集中失效的原因: 1.redis服务器挂掉了. 2.对缓存数据设置了相同的过期时间 ...
- 缓存击穿、缓存失效及热点key的解决方案
分布式缓存是网站服务端经常用到的一种技术,在读多写少的业务场景中,通过使用缓存可以有效地支撑高并发的访问量,对后端的数据库等数据源做到很好地保护.现在市面上有很多分布式缓存,比如Redis.Memca ...
- Redis 缓存穿透,缓存击穿,缓存雪崩的解决方案分析
设计一个缓存系统,不得不要考虑的问题就是:缓存穿透.缓存击穿与失效时的雪崩效应. 一.什么样的数据适合缓存? 分析一个数据是否适合缓存,我们要从访问频率.读写比例.数据一致性等要求去分析. 二.什么 ...
- 【Redis】- 缓存击穿
什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义.如果 ...
- 使用BloomFilter布隆过滤器解决缓存击穿、垃圾邮件识别、集合判重
Bloom Filter是一个占用空间很小.效率很高的随机数据结构,它由一个bit数组和一组Hash算法构成.可用于判断一个元素是否在一个集合中,查询效率很高(1-N,最优能逼近于1). 在很多场景下 ...
- 什么是redis缓存穿透, 缓存雪崩, 缓存击穿
什么是redis? redis是一个非关系型数据库,相对于其他数据库而言,它的查询速度极快,且能承受的瞬时并发量非常的高.所以常常被用来存放网站的缓存,以减少主要数据库(如mysql)的服务器压力. ...
- Redis缓存雪崩、缓存穿透、缓存击穿、缓存降级、缓存预热、缓存更新
Redis缓存能够有效地加速应用的读写速度,就DB来说,Redis成绩已经很惊人了,且不说memcachedb和Tokyo Cabinet之流,就说原版的memcached,速度似乎也只能达到这个级别 ...
- 【原创】分布式之缓存击穿 【原创】自己动手实现静态资源服务器 【原创】自己动手实现JDK动态代理
[原创]分布式之缓存击穿 什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查 ...
随机推荐
- OpenHarmony集成OCR三方库实现文字提取
1. 简介 Tesseract(Apache 2.0 License)是一个可以进行图像OCR识别的C++库,可以跨平台运行 .本样例基于Tesseract库进行适配,使其可以运行在OpenAtom ...
- 对OpenHarmony中LiteOS的内核分析——超时原理和应用
前言 在软件世界里面,超时是一个非常重要的概念.比如 ● 当前线程暂时休眠1秒钟,休眠结束后继续执行 ● 每5秒钟采集一下CPU利用率 ● 数据发送失败,2秒钟以后再试一试 ● 等待某种数据,但最多等 ...
- 1.NCC算法实现及其优化[基础实现篇]
NCC算法实现及其优化 本文将集中探讨一种实现相对简单,效果较好的模板匹配算法(NCC) \[R(x,y)= \frac{ \sum_{x',y'} (T'(x',y') \cdot I'(x+x', ...
- 运动App如何实现端侧后台保活,让运动记录更完整?
你在锻炼健身时,有没有遇到这样的情况?辛辛苦苦锻炼了几小时,却发现App停止了运行,本次运动并没有被记录到App上,从而失去了一个查看完整运动数据的机会? 运动类App是通过手机或者穿戴设备的传感器, ...
- MCM箱模型建模方法及大气O3来源解析
OBM箱模型可用于模拟光化学污染的发生.演变过程,研究臭氧的生成机制和进行敏感性分析,探讨前体物的排放对光化学污染的影响.箱模型通常由化学机理.物理过程.初始条件.输入和输出模块构成,化学机理是其核心 ...
- k8s之operator
背景 数字经济的兴起推动了云计算.物联网.大数据行业的快速蓬勃发展,对数据中心提出了更高的要求,同时,用户对于数据库运维自动化的需求越来越高,数据库即服务的需求越来越强烈. 随着k8s的普及以及云原生 ...
- VIM YouCompleteMe(ycm) 对于Python3第三方库的自动补全【部分解决】
VIM YouCompleteMe(ycm) 对于Python3第三方库的自动补全[部分解决] Python3 学习笔记 问题:VIM 用YouCompleteMe(ycm)自动补全插件时,只能支持P ...
- redis 简单整理——redis 的列表基本结构和命令[四]
前言 简单整理一下redis的列表. 正文 列表(list)类型是用来存储多个有序的字符串,如图2-18所示,a. b.c.d.e五个元素从左到右组成了一个有序的列表,列表中的每个字符串 称为元素(e ...
- 整理ionic 系列——页面生命周期
前言 这是整理ionic的开篇,ionic 就不多介绍了,开发混合app的. 正文 Event Desc ionViewDidLoad 当页面加载的时候触发,仅在页面创建的时候触发一次,如果被缓存了, ...
- sass 基本常识
一.什么是SASS SASS是一种CSS的开发工具,提供了许多便利的写法,大大节省了设计者的时间,使得CSS的开发,变得简单和可维护. 本文总结了SASS的主要用法.我的目标是,有了这篇文章,日常的一 ...