在高并发环境下,缓存与数据库,哪个优先级更高?
- 内容介绍
- 文章标签
- 相关推荐
咱们先别急着上硬核方案,回到原点思考一下。为什么我们需要缓存?
MQ的一大优势就是自带重试机制。如果消费者删除失败了MQ会自动把消息重新投递过来直到成功为止。 我晕... 如果重试多次还是不行,还可以把消息扔进死信队列,人工介入处理。
为什么要休眠?就是为了等那个“倒霉”的读请求把旧数据写进缓存。等它写完了我再来一次删除,把它的劳动成果清空。 雪糕刺客。 这样就能保证到头来缓存里要么是空的,要么是下次读请求重新填的新数据。
纯属忽悠。 优化实践示例:假设一个高并发应用需要优先初始化缓存客户端和数据库连接池,再初始化业务Bean.它拥有比~@Order~更高的优先级,用于决定自动配置类之间的加载顺序.
一、 高并发场景下Bean加载顺序的挑战与优化目标
他破防了。 在高并发或要求快速弹性伸缩的微服务环境中,应用的启动速度至关重要.
事实上... 这种方案虽然好,但对业务代码有一定的侵入性,你得引入MQ客户端,还得处理消息发送的逻辑。不过为了系统的稳定性,这点代价通常是值得的。
太坑了。 这时候,缓存里存的是A的旧值,数据库里是B的新值。两边又不一致了。而且,这个方案还有一个很现实的问题:浪费资源。如果缓存数据不是简单的字符串, 而是要经过复杂的计算、聚合才能得出后来啊,那你每次写数据库都要算一遍,CPU不干了吗?
如果你觉得上面的方案还不够完美, 或者你的系统对一致性要求到了“变态”的程度,那我们还有更高级的招数。
当然架构复杂度也会随之上升,你需要维护Canal、MQ等一系列中间件。 反思一下。 但对于高并发、大数据量的系统来说这笔投入绝对是划算的。
既然先删缓存不行,那再说说一种方案:先写数据库,成功之后再删缓存。
更别提那些“写多读少”的业务场景了你辛辛苦苦算出来写进缓存, 我可是吃过亏的。 后来啊根本没人来读,这不是典型的“瞎忙活”吗?
技术架构的设计,本质上就是在一致性、可用性、分区容错性以及成本之间做权衡。不要迷信某种特定的模式,要结合自己的业务场景,读多还是写多?对实时性要求高不高?团队能否驾驭复杂的中间件,不夸张地说...?
你可能会说那我在代码里直接加个`try-catch`,失败了就重试几次不就行了吗?
未来可期。 所以同步重试只能作为一种辅助手段,不能完全依赖它。我们需要更优雅的异步方案。
希望这篇文章能让你在面对缓存和数据库的纠结时能多一份从容,少一份焦虑。毕竟架构师的价值, 不妨... 不就是在这些两难的抉择中,找到那条最适合当下的路吗?
说到底。 但是这里有个隐藏的逻辑漏洞。假设两个请求一边来了:请求A和请求B都要修改同一条数据。请求A先改了数据库,请求B后改了数据库。按理说数据库里再说说应该是B的值。但是如果网络原因,B更新缓存的操作比A快呢?
还记得前面说的“先删缓存,再写数据库”在并发下的问题吗?我们可以用“缓存双删”来缓解。
不靠谱。 聊了这么多,其实我想表达的是:缓存优先还是数据库?”这个问题上,从来没有一个标准的“正确答案”。
我傻了。 这套方案最大的优点就是彻底解耦。业务接口里只有数据库操作,逻辑简单清晰,性能自然高。缓存删除的工作完全交给了异步的订阅者去做。而且,主要原因是数据是落库的,Binlog里有记录,所以不会丢数据,可靠性极高。
就这样吧... 这就好比你家楼下有个便利店, 而沃尔玛超市大量的I/O操作会让磁盘累得冒烟,CPU和内存资源也会被迅速耗尽。
但如果你面对的是百万、 千万级的流量,那“订阅Binlog+异步删除”可能就是你不得不上的选择。
所以缓存的主要目的, 就是把那些“热点数据”——也就是大家经常查、经常用的数据,临时搬到内存里。这样一来后续的查询请求直接从内存拿,速度飞起,数据库的压力也小了。这听起来很美好,对吧,我当场石化。?
如果你的系统只是几千几万的并发, 那“先写数据库,再删缓存”加上简单的重试,可能就足够了。别为了那一点点按道理讲的不一致,把系统搞得过于复杂,拖进度。。
踩个点。 除了MQ,还有一种比较“笨”但有效的办法:定时任务。
我始终觉得... 业务代码在更新完数据库后如果删除缓存失败, 或者不管成功与否,我们都发送一条“删除缓存”的消息到MQ服务器。然后有一个专门的消费者服务,监听这个消息,收到消息后去施行真正的删除缓存操作。
摆烂... 假设有一写一读操作,先操作缓存,在操作数据库.OK,酱紫,就有问题了吧,老数据入到缓存了,每次读都是老数据啦,缓存与数据与数据库数...
具体操作是:1. 先删除缓存。2. 更新数据库。 我好了。 3. 休眠一小会儿。4. 删除缓存。
所以先写缓存再写数据库,这种方案基本没人用,风险太大。
千万别这么干!这绝对是个大坑。你想啊,如果你刚把缓存更新成了最新值,后来啊写数据库的时候网络抖了一下或者数据库挂了事务回滚了。这时候会发生什么?缓存里是新数据,数据库里是旧数据。这就相当于你告诉全世界“我发财了”,后来啊你银行卡里余额还是零。这就是典型的“脏数据”问题,一旦发生,后果不堪设想。
优化一下。 很多时候, 我们为了追求那极致的响应速度,恨不得把所有数据都塞进Redis里让数据库“冷宫”吃灰。但现实往往很骨感,一旦数据出现不一致,老板的咆哮声可能比服务器的风扇声还要大。今天我们就抛开那些教科书式的教条, 用最接地气的方式,聊聊在高并发这个大熔炉里缓存与数据库到底该如何相处。
既然直接更新缓存有这么多坑,那我们换个思路:我不更新了我直接把缓存删了行不行?下次有人来查的时候,再去数据库读,然后回填缓存,这叫“懒加载”,纯正。。
双写操作,先操作缓存,在操作数据库。。高并发-高负载数据库解决方案 简单来说... .先说说,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用.
再更新缓存 如果一边有两个写请求需要更新数据, 每个写请求都先更新数据库再更新缓存,在并发场景可能会出现数据不一致的情况。 先更新数据库, 再更新缓存 如上图的施行过程: 写请求1更新数据库,将 age字段更新为18; 写请求2更新数据库,将 age字段更新为20; 写请求2更新缓存,缓存 age设置为20; 写请求1更新缓
在大型系统中,为了减少数据库压力通常会引入缓存机制,一旦引入缓存又很容 欧了! 易造成缓存和数据库数据不一致,导致用户看到的是旧数据。.为了减少数...
躺平... 有些朋友觉得, 既然缓存快,那我先更新缓存,再慢吞吞地去更新数据库,这样用户不就能最快看到新数据了吗?
这是目前大厂里非常流行的一种方案,堪称“优雅”。
这也行? 结局很悲惨:数据库是新值,缓存里却稳稳地躺着旧值。而且这个旧值要一直待到过期,或者下次有写操作来清理它。这明摆着不是我们想要的后来啊。
哎,对! 想法很丰满,现实很骨感。如果你的业务接口本身并发量就很高,你在这个接口里做同步重试,会阻塞当前线程,严重影响接口的性能。万一重试几次都失败,客户端是不是要等到天荒地老?
高并发场景下数据库与缓存的性能差异分析.内存淘汰策略:内存达到阈值时,自动淘汰过期或低优先级数据,释放内存。.在高并发场景中,缓存通常能稳定应对大量请求,而数据库却容易崩溃.,试着...
这个方案在低并发下没问题,但在高并发下又要出幺蛾子了。想象一下此时有一个写请求D,要把数据改成新值。它先把缓存删了。就在这一瞬间,一个读请求C过来了。它一看缓存没数据,赶紧去数据库查。好巧不巧,它查到的是D还没写入的旧值。然后C把这个旧值写回了缓存,心情复杂。。
这个方案虽然能解决问题,但那个“休眠”时间很难把控。太短了没用,太长了影响性能。 优化一下。 而且,如果第二次删除又失败了还得配合重试机制使用。
既然“先写数据库, 再删缓存”是目前的优选,那我们就得想办法解决“删缓存失败”这个致命伤。 结果你猜怎么着? 这时候,就需要祭出我们的“重试机制”大法了。
我们可以搞一张“重试表”。当业务代码更新数据库后如果删除缓存失败,就把这条数据的key写入重试表。然后写一个定时任务,比如每分钟跑一次扫描这张表,把里面的缓存key删掉,删成功后就把记录从表里删掉。
它的核心思想是:业务代码只管操作数据库,完全不用操心缓存,让我们一起...!
但问题来了:便利店的数据如果和沃尔玛不一样了怎么办?你刚这个问题会被无限放大,反思一下。。
cache miss 4)线程B从DB获取最新数据 5)线程B一边set cache 这种方案没有明显的并发问题, 但是有可能步骤二删除缓存失败,虽然概率比较小,优于方案一和方案二,平时工作中也是使用方案三。 综上对比,我们一般采用方案三,但是有没有完美全解决方案三的弊端的方法呢? 缓存维护方案四 这个是方案三的改进方案, 都是先操作数据库再操作缓存,我们来看一下流程图: 通过,性价比超高。
我悟了。 但如果在高并发的业务场景中,写数据库和写缓存,都属于远程操作.也就是说:在高并发场景中,如果多个线程一边施行先写数据库,再写缓存的操作,可能会出现数据库是新值,而缓存中是旧值,两边数据不一致的情况。.
高并发场景下的缓存+数据库双写不一致问题分析与解决方案设计.docx.比方说,在高并发场景下,某个数据项的缓存可能会被清理掉,而数据库中的数据却没有被更新,这样就会导致缓存和数据库中的数据不一致.
给力。 原文写的很好:高并发下先操作数据库还是先操作缓存.TCSQL高并发实时列表缓存数据库原理图.
这个方案的缺点是实时性差点意思。定时任务有延迟,可能数据库都改了半天了缓存才被删掉。 共勉。 但对于一些对实时性要求没那么苛刻的业务,这招挺好使,而且技术实现成本低。
紧接着,D把新值写入了数据库。
挺好。 既然要保证一致,那在写数据的时候,我们该怎么处理缓存呢?目前业界主流的玩法大概有四种,咱们一个个来扒一扒它们的底裤。
我们可以引入消息队列,比如RabbitMQ或者RocketMQ。流程是这样的:
于是方案变成了:先删缓存,再写数据库。
但是这并不意味着它完美无缺。如果删缓存的那一步失败了怎么办?比如Redis抖动了一下没删掉。那数据库是新值,缓存还是旧值,不一致的问题依然存在,我当场石化。。
摸鱼。 我们利用MySQL的Binlog机制, 使用像Canal这样的中间件,成MySQL的从库,订阅Binlog。一旦检测到数据库有数据变更, Canal就会解析出变更的数据,然后发送到MQ,或者直接调用程序去删除对应的缓存。
为什么这个方案被很多人推崇?主要原因是从概率上讲,写数据库的操作通常比删缓存要慢。所以读请求一般比写请求先完成。也就是说当写请求准备删缓存的时候,读请求早就把旧数据读走并结束了。这时候删缓存,影响最小。
说起来... 那反过来了呢?先更新数据库,成功之后再把缓存更新一遍。这听起来稳妥多了毕竟数据库是源头。
而数据库优化则涉及到索引、查询优化等方面,这些都是在高并发环境下不可或缺的技术。 纯属忽悠。 .队列的高级用法包括延迟任务、优先级队列等.高并发优...
咱们先别急着上硬核方案,回到原点思考一下。为什么我们需要缓存?
MQ的一大优势就是自带重试机制。如果消费者删除失败了MQ会自动把消息重新投递过来直到成功为止。 我晕... 如果重试多次还是不行,还可以把消息扔进死信队列,人工介入处理。
为什么要休眠?就是为了等那个“倒霉”的读请求把旧数据写进缓存。等它写完了我再来一次删除,把它的劳动成果清空。 雪糕刺客。 这样就能保证到头来缓存里要么是空的,要么是下次读请求重新填的新数据。
纯属忽悠。 优化实践示例:假设一个高并发应用需要优先初始化缓存客户端和数据库连接池,再初始化业务Bean.它拥有比~@Order~更高的优先级,用于决定自动配置类之间的加载顺序.
一、 高并发场景下Bean加载顺序的挑战与优化目标
他破防了。 在高并发或要求快速弹性伸缩的微服务环境中,应用的启动速度至关重要.
事实上... 这种方案虽然好,但对业务代码有一定的侵入性,你得引入MQ客户端,还得处理消息发送的逻辑。不过为了系统的稳定性,这点代价通常是值得的。
太坑了。 这时候,缓存里存的是A的旧值,数据库里是B的新值。两边又不一致了。而且,这个方案还有一个很现实的问题:浪费资源。如果缓存数据不是简单的字符串, 而是要经过复杂的计算、聚合才能得出后来啊,那你每次写数据库都要算一遍,CPU不干了吗?
如果你觉得上面的方案还不够完美, 或者你的系统对一致性要求到了“变态”的程度,那我们还有更高级的招数。
当然架构复杂度也会随之上升,你需要维护Canal、MQ等一系列中间件。 反思一下。 但对于高并发、大数据量的系统来说这笔投入绝对是划算的。
既然先删缓存不行,那再说说一种方案:先写数据库,成功之后再删缓存。
更别提那些“写多读少”的业务场景了你辛辛苦苦算出来写进缓存, 我可是吃过亏的。 后来啊根本没人来读,这不是典型的“瞎忙活”吗?
技术架构的设计,本质上就是在一致性、可用性、分区容错性以及成本之间做权衡。不要迷信某种特定的模式,要结合自己的业务场景,读多还是写多?对实时性要求高不高?团队能否驾驭复杂的中间件,不夸张地说...?
你可能会说那我在代码里直接加个`try-catch`,失败了就重试几次不就行了吗?
未来可期。 所以同步重试只能作为一种辅助手段,不能完全依赖它。我们需要更优雅的异步方案。
希望这篇文章能让你在面对缓存和数据库的纠结时能多一份从容,少一份焦虑。毕竟架构师的价值, 不妨... 不就是在这些两难的抉择中,找到那条最适合当下的路吗?
说到底。 但是这里有个隐藏的逻辑漏洞。假设两个请求一边来了:请求A和请求B都要修改同一条数据。请求A先改了数据库,请求B后改了数据库。按理说数据库里再说说应该是B的值。但是如果网络原因,B更新缓存的操作比A快呢?
还记得前面说的“先删缓存,再写数据库”在并发下的问题吗?我们可以用“缓存双删”来缓解。
不靠谱。 聊了这么多,其实我想表达的是:缓存优先还是数据库?”这个问题上,从来没有一个标准的“正确答案”。
我傻了。 这套方案最大的优点就是彻底解耦。业务接口里只有数据库操作,逻辑简单清晰,性能自然高。缓存删除的工作完全交给了异步的订阅者去做。而且,主要原因是数据是落库的,Binlog里有记录,所以不会丢数据,可靠性极高。
就这样吧... 这就好比你家楼下有个便利店, 而沃尔玛超市大量的I/O操作会让磁盘累得冒烟,CPU和内存资源也会被迅速耗尽。
但如果你面对的是百万、 千万级的流量,那“订阅Binlog+异步删除”可能就是你不得不上的选择。
所以缓存的主要目的, 就是把那些“热点数据”——也就是大家经常查、经常用的数据,临时搬到内存里。这样一来后续的查询请求直接从内存拿,速度飞起,数据库的压力也小了。这听起来很美好,对吧,我当场石化。?
如果你的系统只是几千几万的并发, 那“先写数据库,再删缓存”加上简单的重试,可能就足够了。别为了那一点点按道理讲的不一致,把系统搞得过于复杂,拖进度。。
踩个点。 除了MQ,还有一种比较“笨”但有效的办法:定时任务。
我始终觉得... 业务代码在更新完数据库后如果删除缓存失败, 或者不管成功与否,我们都发送一条“删除缓存”的消息到MQ服务器。然后有一个专门的消费者服务,监听这个消息,收到消息后去施行真正的删除缓存操作。
摆烂... 假设有一写一读操作,先操作缓存,在操作数据库.OK,酱紫,就有问题了吧,老数据入到缓存了,每次读都是老数据啦,缓存与数据与数据库数...
具体操作是:1. 先删除缓存。2. 更新数据库。 我好了。 3. 休眠一小会儿。4. 删除缓存。
所以先写缓存再写数据库,这种方案基本没人用,风险太大。
千万别这么干!这绝对是个大坑。你想啊,如果你刚把缓存更新成了最新值,后来啊写数据库的时候网络抖了一下或者数据库挂了事务回滚了。这时候会发生什么?缓存里是新数据,数据库里是旧数据。这就相当于你告诉全世界“我发财了”,后来啊你银行卡里余额还是零。这就是典型的“脏数据”问题,一旦发生,后果不堪设想。
优化一下。 很多时候, 我们为了追求那极致的响应速度,恨不得把所有数据都塞进Redis里让数据库“冷宫”吃灰。但现实往往很骨感,一旦数据出现不一致,老板的咆哮声可能比服务器的风扇声还要大。今天我们就抛开那些教科书式的教条, 用最接地气的方式,聊聊在高并发这个大熔炉里缓存与数据库到底该如何相处。
既然直接更新缓存有这么多坑,那我们换个思路:我不更新了我直接把缓存删了行不行?下次有人来查的时候,再去数据库读,然后回填缓存,这叫“懒加载”,纯正。。
双写操作,先操作缓存,在操作数据库。。高并发-高负载数据库解决方案 简单来说... .先说说,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用.
再更新缓存 如果一边有两个写请求需要更新数据, 每个写请求都先更新数据库再更新缓存,在并发场景可能会出现数据不一致的情况。 先更新数据库, 再更新缓存 如上图的施行过程: 写请求1更新数据库,将 age字段更新为18; 写请求2更新数据库,将 age字段更新为20; 写请求2更新缓存,缓存 age设置为20; 写请求1更新缓
在大型系统中,为了减少数据库压力通常会引入缓存机制,一旦引入缓存又很容 欧了! 易造成缓存和数据库数据不一致,导致用户看到的是旧数据。.为了减少数...
躺平... 有些朋友觉得, 既然缓存快,那我先更新缓存,再慢吞吞地去更新数据库,这样用户不就能最快看到新数据了吗?
这是目前大厂里非常流行的一种方案,堪称“优雅”。
这也行? 结局很悲惨:数据库是新值,缓存里却稳稳地躺着旧值。而且这个旧值要一直待到过期,或者下次有写操作来清理它。这明摆着不是我们想要的后来啊。
哎,对! 想法很丰满,现实很骨感。如果你的业务接口本身并发量就很高,你在这个接口里做同步重试,会阻塞当前线程,严重影响接口的性能。万一重试几次都失败,客户端是不是要等到天荒地老?
高并发场景下数据库与缓存的性能差异分析.内存淘汰策略:内存达到阈值时,自动淘汰过期或低优先级数据,释放内存。.在高并发场景中,缓存通常能稳定应对大量请求,而数据库却容易崩溃.,试着...
这个方案在低并发下没问题,但在高并发下又要出幺蛾子了。想象一下此时有一个写请求D,要把数据改成新值。它先把缓存删了。就在这一瞬间,一个读请求C过来了。它一看缓存没数据,赶紧去数据库查。好巧不巧,它查到的是D还没写入的旧值。然后C把这个旧值写回了缓存,心情复杂。。
这个方案虽然能解决问题,但那个“休眠”时间很难把控。太短了没用,太长了影响性能。 优化一下。 而且,如果第二次删除又失败了还得配合重试机制使用。
既然“先写数据库, 再删缓存”是目前的优选,那我们就得想办法解决“删缓存失败”这个致命伤。 结果你猜怎么着? 这时候,就需要祭出我们的“重试机制”大法了。
我们可以搞一张“重试表”。当业务代码更新数据库后如果删除缓存失败,就把这条数据的key写入重试表。然后写一个定时任务,比如每分钟跑一次扫描这张表,把里面的缓存key删掉,删成功后就把记录从表里删掉。
它的核心思想是:业务代码只管操作数据库,完全不用操心缓存,让我们一起...!
但问题来了:便利店的数据如果和沃尔玛不一样了怎么办?你刚这个问题会被无限放大,反思一下。。
cache miss 4)线程B从DB获取最新数据 5)线程B一边set cache 这种方案没有明显的并发问题, 但是有可能步骤二删除缓存失败,虽然概率比较小,优于方案一和方案二,平时工作中也是使用方案三。 综上对比,我们一般采用方案三,但是有没有完美全解决方案三的弊端的方法呢? 缓存维护方案四 这个是方案三的改进方案, 都是先操作数据库再操作缓存,我们来看一下流程图: 通过,性价比超高。
我悟了。 但如果在高并发的业务场景中,写数据库和写缓存,都属于远程操作.也就是说:在高并发场景中,如果多个线程一边施行先写数据库,再写缓存的操作,可能会出现数据库是新值,而缓存中是旧值,两边数据不一致的情况。.
高并发场景下的缓存+数据库双写不一致问题分析与解决方案设计.docx.比方说,在高并发场景下,某个数据项的缓存可能会被清理掉,而数据库中的数据却没有被更新,这样就会导致缓存和数据库中的数据不一致.
给力。 原文写的很好:高并发下先操作数据库还是先操作缓存.TCSQL高并发实时列表缓存数据库原理图.
这个方案的缺点是实时性差点意思。定时任务有延迟,可能数据库都改了半天了缓存才被删掉。 共勉。 但对于一些对实时性要求没那么苛刻的业务,这招挺好使,而且技术实现成本低。
紧接着,D把新值写入了数据库。
挺好。 既然要保证一致,那在写数据的时候,我们该怎么处理缓存呢?目前业界主流的玩法大概有四种,咱们一个个来扒一扒它们的底裤。
我们可以引入消息队列,比如RabbitMQ或者RocketMQ。流程是这样的:
于是方案变成了:先删缓存,再写数据库。
但是这并不意味着它完美无缺。如果删缓存的那一步失败了怎么办?比如Redis抖动了一下没删掉。那数据库是新值,缓存还是旧值,不一致的问题依然存在,我当场石化。。
摸鱼。 我们利用MySQL的Binlog机制, 使用像Canal这样的中间件,成MySQL的从库,订阅Binlog。一旦检测到数据库有数据变更, Canal就会解析出变更的数据,然后发送到MQ,或者直接调用程序去删除对应的缓存。
为什么这个方案被很多人推崇?主要原因是从概率上讲,写数据库的操作通常比删缓存要慢。所以读请求一般比写请求先完成。也就是说当写请求准备删缓存的时候,读请求早就把旧数据读走并结束了。这时候删缓存,影响最小。
说起来... 那反过来了呢?先更新数据库,成功之后再把缓存更新一遍。这听起来稳妥多了毕竟数据库是源头。
而数据库优化则涉及到索引、查询优化等方面,这些都是在高并发环境下不可或缺的技术。 纯属忽悠。 .队列的高级用法包括延迟任务、优先级队列等.高并发优...

