49.Go避免大量并发访问DB、避免缓存击穿、缓存穿透、缓存雪崩以及使用延迟双删保证数据一致性

文章目录

  • 一、在高并发下,如何避免大量请求直接访问数据库?
  • 二、避免缓存击穿
  • 二、避免缓存穿透
  • 三、避免缓存雪崩
  • 四、延迟双删保证数据一致性
  • 五、在使用 Go 的 time.AfterFunc 函数时,如果删除缓存操作失败怎么办?

MySQLRedis是工作中最常见的两个组件,那么在使用过程中也有一些常见的问题和解决办法。本文通过 Go实现介绍它们。

一、在高并发下,如何避免大量请求直接访问数据库?

在高并发下,如果让大量请求直接访问数据库,可能会造成数据库压力过大,响应延迟上升,严重时甚至可能导致数据库崩溃。所以我们需要采取一些措施来保护数据库,避免大量请求直接访问数据库。以下是一些常见的策略:

  1. 使用缓存:缓存是最常见、最直接的方法,可以大大减少数据库的请求压力。对于读多写少的场景尤其有效。不仅如此,合理使用缓存,还可以提高系统的响应速度。但是在使用缓存时要考虑BigKeyHotKey、缓存击穿、缓存穿透、缓存雪崩以及数据一致性等众多问题。HotKeyBigKey可以看本人之前的两篇文章:
    HotKey:29.Go处理Redis HotKey
    BigKey: 30.Go处理Redis BigKey

  2. 限流:使用限流算法(如漏桶算法、令牌桶算法)来对访问进行限制,保证系统在可接受的压力范围内运行,防止数据库被过多的请求冲垮。
    限流:13. Go中常见限流算法示例代码

  3. 熔断机制:引入熔断器,当侦测到某个服务的错误次数过多(如因数据库连接问题导致的错误)时,熔断该服务的所有请求,直到服务恢复。
    限流与熔断:48.Go简要实现令牌桶限流与熔断器并集成到Gin框架中

  4. 负载均衡:如果有足够的资源,可以采用负载均衡策略,如数据库读写分离,主从复制等,将读写压力分散到多个数据库节点上。
    负载均衡:15. Go实现负载均衡算法

  5. 使用队列:对于非实时性的数据请求,可以采用异步处理的方式,将请求先放入队列中,然后通过队列对数据库请求进行削峰填谷,避免大量请求同时涌向数据库。
    MQ:28.windows安装kafka,Go操作kafka示例

  6. 数据库优化:适当地对数据库进行优化,如合理的索引、合理的表结构设计、SQL 函数优化等,都可以提高数据库处理请求的能力。

以上就是一些避免数据库在高并发下被大量请求直接访问的策略,可以根据具体的场景和需求选择适合的策略。

二、避免缓存击穿

缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的Key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击,即瞬时的高并发击穿了缓存去请求DB

缓存雪崩和缓存击穿的区别在于缓存击穿针对某一Key缓存,缓存雪崩则是很多Key。

由于缓存击穿是某个热点Key突然过期导致的,那么我们继续把它加载进缓存就可以了,但是不能让所有请求都去加载,只需要放行一个请求去加载,之后其他请求直接从缓存获取,避免高并发流量打挂DB,通过互斥锁即可实现。

互斥锁:在读取缓存的过程中,如果缓存未命中,则添加锁并从数据库中查询。这样可以避免在高并发下,大量的请求直接访问数据库。

注意:使用double check机制,此外,从DB查询后,如果DB也不存在,应该缓存一个空对象,否则这些高并发请求会继续请求DB。在设置空对象时,也可以设置一个较短的过期时间,避免长时间缓存空对象。

v, found := cache.Get(key)
if !found {lock.Lock()defer lock.Unlock()// double checkv, found = cache.Get(key)if !found {v, err := db.Get(key)if err != nil {// handle errorreturn nil, err}if v == nil {v = EmptyObject}cache.Set(key, v, cache.DefaultExpiration)}
}
return v, nil

二、避免缓存穿透

缓存穿透是指用户不断对一个缓存和数据库都不存在的数据进行访问,对于这种情况,由于既在缓存中查不到,也在数据库中查不到,于是每次都会对数据库进行一次查询,造成数据库压力增大,这种请求有可能是一个恶意攻击。

Go 语言中我们可以使用以下策略来避免缓存穿透:

缓存空对象:把空结果也进行缓存,当后续请求再次查询时,即使查不到数据,也会在缓存中得到一个空结果,而不会再对数据库进行查询。同时,为了避免未来的查询都返回空结果,需要对空结果设置一个较短的过期时间。

伪代码如下:

v, found := cache.Get(key)
if found {return v
}v, err := db.Get(key)
if err != nil {// handle errorreturn nil, err
}if v == nil {v = EmptyObjectcache.Set(key, v, cache.DefaultExpiration)
}
return v, nil

使用布隆过滤器:布隆过滤器(Bloom filter)是一种用于测试一个元素是否在一个集合中的数据结构。由于它的存储效率高且可以非常快速的查询,所以常常被用来过滤掉一部分肯定不存在的数据,避免了对数据库的无谓请求,在大数据量查找中有很高效率。

伪代码如下:

if !bloomFilter.Exists(key) {return nil
}v, found := cache.Get(key)
if found {return v
}v, err := db.Get(key)
if err != nil {// handle errorreturn nil, err
}if v == nil {v = EmptyObject
}cache.Set(key, v, cache.DefaultExpiration)
return v, nil

在上面的示例中,如果布隆过滤器中不存在请求中的 key,则直接返回,不对数据库做查询。

上述两种方法可以有效应对缓存穿透,能够减轻数据库的压力,提升程序的响应速度。

三、避免缓存雪崩

缓存雪崩是指在缓存系统中,大量数据同时过期,在访问频率高的情况下可能会引起数据库的过载。

在Go语言中,我们可以采用以下措施来避免缓存雪崩:

1、设置缓存失效时间的随机性
使得每一个key的失效时间都是随机的,防止所有缓存在同一时刻全部失效。例如,我们可以在原有的失效时间基础上,增加一个随机的延长时间。

expires := baseExpires + time.Duration(rand.Intn(randExpires)) * time.Second
cache.Set(key, value, expires)

2、使用数据版本控制
通过在每次缓存数据时将数据打上版本(可以是时间戳、或是递增版本号等),每次访问时,先访问缓存,如果缓存不存在或者版本低于当前的版本,就更新缓存数据。这样,即使缓存失效,也可以由单一线程去做更新,其它请求只需要等待即可。

func Get(key string, currentVersion int) (string, error) {v, version, found := cache.GetWithVersion(key)if found && version >= currentVersion {return v, nil}// Single flight to load from the database and put to the cache.// Usually done with a lock or using sync.Once type of logic.// ...return v, nil
}

3、熔断机制和降级

在系统压力过大或者服务不可用的情况下,可以进行熔断降级,比如返回一些默认值,或者从备份缓存中读数据。

整体来说,防止缓存雪崩主要是预防工作以及在系统异常时的快速应对。每一个解决方案都有其应用场景,需要根据具体业务情况进行选择。

四、延迟双删保证数据一致性

使用到缓存,一般就需要考虑缓存与数据库的一致性。如更新时,是先更新缓存还是先更新DB,或者是先删除缓存还是先删除DB、或者是否要通过监听Binlog同步缓存,或者做一些其他旁路校验等。方案很多,需要针对当前业务是否能够容忍缓存与DB的不一致,以及容忍的程度如何来做具体涉设。但是最常用的还是延迟双删方案,成本低,容易实现,且能基本保障一致性。

延迟双删是解决缓存更新一致性问题的一个策略,具体策略如下:

  1. 先删除缓存
  2. 再更新数据库
  3. 最后延时删除缓存

这样做的目的是为了应对并发情况下缓存与数据库数据不一致的问题。

Go语言实现延迟双删的一个简单示例如下:

// 先删除缓存
cache.Delete(key)// 更新数据库
err := db.Update(key, value)
if err != nil {fmt.Printf("DB update error: %v", err)return
}// 延迟删除缓存
time.AfterFunc(time.Duration(delayMillisecond)*time.Millisecond, func() {cache.Delete(key)
})

上述代码的逻辑:

  • 首先,通过 cache.Delete(key) 删除旧的缓存数据。
  • 然后,通过 db.Update(key, value) 更新数据库数据。
  • 最后,使用 Gotime.AfterFunc 函数,实现一段时间后再次删除缓存。时间可以依据实际业务情况设定。

这种方式可以在大部分场景下确保缓存和数据库的数据一致,但还是存在极端情况下的问题,比如在第二次删除缓存之前,有其他请求把旧的数据加载到了缓存。这种情况的出现几率较小,如果业务对此有较高的要求,可能需要使用更严格的方案,如监听Binlog同步缓存。

五、在使用 Go 的 time.AfterFunc 函数时,如果删除缓存操作失败怎么办?

在使用time.AfterFunc时,如果删除缓存操作失败,最常见的处理办法是进行重试操作。不过,在设置重试次数和重试延迟时,应谨慎考虑以防止无效操作导致系统资源的浪费。

func deleteCacheWithRetry(key string, retryTimes int, delay time.Duration) {for i := 0; i < retryTimes; i++ {// 尝试删除缓存err := cache.Delete(key)if err == nil {// 删除成功return}// 如果删除失败,则等待一段时间再重试time.Sleep(delay)}// 在此处处理连续失败的情况,例如记录日志、发送告警等fmt.Printf("Failed to delete cache for key %s after %d attempts\n", key, retryTimes)
}// 在 time.AfterFunc 中使用
time.AfterFunc(time.Duration(delayMillisecond)*time.Millisecond, func() {deleteCacheWithRetry(key, 3, 1*time.Second)
})

在以上代码中,我们定义了一个名为 deleteCacheWithRetry 的函数,它接受一个缓存键、重试次数和每次重试延迟的时间。它将尝试删除缓存,如果失败,将等待一段时间后重试,直到达到最大重试次数。如果超过重试次数仍未成功,将通过日志记录这个异常情况。
当然,具体的处理方式要根据项目具体需求和场景去判断,以上只是一个参考示例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/212369.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/212369.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue自定义指令实现按钮只允许点击一次

vue自定义指令实现按钮只允许点击一次 vue自定义指令实现按钮只允许点击一次 这个例子中创建了一个名为 click-once 的自定义指令&#xff0c;通过 bind 钩子函数给元素绑定了一个点击事件&#xff0c;并且利用一个变量 clicked 控制了按钮只能点击一次的行为。在点击后会执行传…

【ITK库学习】使用itk库进行图像滤波ImageFilter:Voting滤波器

目录 1、itkVotingBinaryImageFilter2、itkVotingBinaryHoleFillingImageFilter 洞穴充填滤波器3、itkVotingBinaryIterativeHoleFillingImageFilter4、itkLabelVotingImageFilter 1、itkVotingBinaryImageFilter 该类是一个基类&#xff0c;用于根据前景和背景像素的邻域投票…

【数据结构实践课设】新生报道注册管理信息系统

目录 1.主要框架 2.写入文件 3.读取文件 4.注册学生信息 5.增加学生信息 6.删除学生信息 7.按姓名查询 8.按班级查询 9.按专业查询 10.打印学生信息 11.完整代码 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所…

git commit语义规范

合理的应当如 [header]fix(core): remove ....(#33949) These .... RP Close #33949(可选) Header可选 代码类 新增功能(feat) 修复缺陷(fix) 改进性能(perf) 格式化代码(style) 优化代码(refactor) 非代码类 更新测试代码(test) 部署相关变更(ci) 文档类变更(do…

【Linux】第二十七站:内存管理与文件页缓冲区

文章目录 一、物理内存和磁盘交换数据的最小单位二、操作系统如何管理内存三、文件的页缓冲区四、基数树or基数&#xff08;字典树&#xff09;五、总结 一、物理内存和磁盘交换数据的最小单位 我们知道系统当中除了进程管理、文件管理以外&#xff0c;还有内存管理 内存的本质…

思科最新版Cisco Packet Tracer 8.2.1安装

思科最新版Cisco Packet Tracer 8.2.1安装 一. 注册并登录CISCO账号二. 下载 Cisco Packet Tracer 8.2.1三. 安装四. 汉化五. cisco packet tracer教学文档六. 正常使用图 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新…

[香橙派]orange pi zero 3 烧录Ubuntu系统镜像——无需HDMI数据线安装

一、前言 本文我们将介绍如何使用orange pi zero 3 安装Ubuntu系统&#xff0c;本文相关步骤均参考自开发手册。 二、实施准备 根据开发手册中所提到的&#xff0c;我们应该拥有如下配件: 1.orange pi zero 3 开发板 2.TF 卡——最小 8GB 容量的 class10 级或以上的高速闪迪卡。…

鸿蒙OS应用开发之语句

在程序开发中&#xff0c;已经有上面的运算符和数据类型了&#xff0c;可以满足了大部的需求&#xff0c;但是这些程序还是比较简单的计算和逻辑运算&#xff0c;如果需要复杂的计算和逻辑处理&#xff0c;就需要采用复杂逻辑程序块来处理了&#xff0c;也就是复杂条件语句才能…

nn.Sequential|nn.ModuleDict|nn.ModuleList 详解

文章目录 1、简介2、三者之间的区别3、如何让nn.ModuleList 和nn.ModuleDict实现推理3.1 方案1: 实现forward函数3.2 方案2: 将nn.ModuleList 和nn.ModuleDict转换为nn.Sequential4、nn.ModuleDict、nn.ModuleList 的区别5、nn.ModuleList 、 nn.ModuleDict 与 Python list、…

模型 心流

本系列文章 主要是 分享模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。完全投入其中。 1 心流的应用 1.1 优秀运动员的心流体验 迈克尔乔丹&#xff08;Michael Jordan&#xff09;&#xff1a;篮球之神乔丹在比赛中经常进入心流状态&#xff0c;他曾表示&#xff…

DIY手工艺作坊网站建设的作用如何

我国文化悠久流长&#xff0c;很多手工艺品制作技术放在如今依然有很高的需求度&#xff0c;加之现代新增的技艺&#xff0c;样式多且艺术性强&#xff0c;比如常见的陶器手工制作技术&#xff0c;当然还有更多。 而对相关作坊来说&#xff0c;除了艺术传承外&#xff0c;还需…

接触刚性环境任务下的机器人力控(阻抗)性能测试

内涵 接触刚性环境任务下的机器人力控&#xff08;阻抗&#xff09;性能测试旨在评估机器人在与刚性物体交互时的性能表现。这种测试通过调整机器人的控制参数&#xff0c;如期望刚度和期望阻尼等&#xff0c;并分析记录的数据&#xff0c;旨在确保机器人能够在执行任务时保持…

短剧分销小程序/APP开发:开启短剧收益时代

今年&#xff0c;短剧火爆出圈&#xff0c;市场规模将达至200亿元至300亿元。国内全全平台付费短剧日充值金额为6000万元&#xff0c;短剧作为一种“快餐式”文化迅速爆火。 短剧契合了观众娱乐时间碎片化的发展趋势&#xff0c;相比于传统的电视剧&#xff0c;短剧节奏快、剧…

Nacos源码解读10——配置中心的客户端怎么处理服务端推送的配置信息变更

自动装配 SpringBoot 自动装配机制 加载 WEB/INF spring.factories org.springframework.cloud.bootstrap.BootstrapConfiguration\ com.alibaba.cloud.nacos.NacosConfigBootstrapConfigurationjava Configuration(proxyBeanMethods false) ConditionalOnProperty(name &q…

MongoDB的连接数据库,创建、删除数据库,创建、删除集合命令

本文主要介绍MongoDB的连接数据库&#xff0c;创建、删除数据库&#xff0c;创建、删除集合命令。 目录 MongoDB连接数据库连接到本地 MongoDB 实例连接到远程 MongoDB 实例 MongoDB创建和删除数据库MongoDB创建和删除集合创建集合删除集合 MongoDB连接数据库 连接 MongoDB 数…

P1317 低洼地题解

题目 一组数&#xff0c;分别表示地平线的高度变化。高度值为整数&#xff0c;相邻高度用直线连接。找出并统计有多少个可能积水的低洼地&#xff1f; 如图&#xff1a;地高变化为 [0,1,0,2,1,2,0,0,2,0]。 输入输出格式 输入格式 两行&#xff0c;第一行n, 表示有n个数。第…

Spark DataFrame和Dataset使用例子

文章目录 1、基本操作1.1、创建SparkSession1.2、创建DataFrames1.3、创建Dataset操作1.4、运行sql查询1.5、创建全局临时视图1.6、创建Datasets1.7、与rdd进行互操作1.7.1、使用反射推断模式1.7.2、以编程方式指定模式 2、完整的测试例子 1、基本操作 1.1、创建SparkSession …

openGauss学习笔记-151 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_basebackup

文章目录 openGauss学习笔记-151 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_basebackup151.1 背景信息151.2 前提条件151.3 语法151.4 示例151.5 从备份文件恢复数据 openGauss学习笔记-151 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_basebackup 151.1 …

NeuralKG运行备忘

环境配置&#xff1a; conda create -n neuralkg python3.8 conda activate neuralkg pip install torch1.9.1cu111 -f https://download.pytorch.org/whl/torch_stable.html pip install dgl-cu111 dglgo -f https://data.dgl.ai/wheels/repo.html pip install neuralkg! co…

基于java swing 药品销售管理系统

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…