Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

缓存与数据库的双写一致性 | Consistency between redis cache and sql database #54

Open
BigBlackSheep opened this issue Apr 3, 2019 · 33 comments
Labels

Comments

@BigBlackSheep
Copy link

@BigBlackSheep BigBlackSheep commented Apr 3, 2019

诚如文章所说 为避免数据不一致 给出的思路是:先删除缓存,再更新数据库
那么如果现在有两个线程 一个A更新 一个B读取,当A线程在更新的时候先把缓存给删掉了然后去做更新数据库的时候,B线程来读取数据的时候发现缓存没有数据(被A给删掉了)然后去读取数据库把数据读取出来放到了缓存中,这个时候A线程再去做更新数据库的操作...那么好戏来了...数据库和缓存真的不一致了. 请问要如何解决呢?

@pangjianhua
Copy link

@pangjianhua pangjianhua commented Apr 3, 2019

双写一致性校验应该可以

@BigBlackSheep
Copy link
Author

@BigBlackSheep BigBlackSheep commented Apr 9, 2019

双写一致性校验应该可以

其实根据 《Cache-Aside pattern》先更新数据库再删缓存就可以啦

@yanglbme yanglbme added the help wanted label Apr 9, 2019
@wbliubin
Copy link

@wbliubin wbliubin commented Apr 22, 2019

我怎么感觉先删除缓存,再更新数据库 和 先更新数据库再删除缓存是一样的都会有问题呢
假如是先更新数据库再 删缓存,此时如果数据库还在更新中,一个线程进行了读取,不是也读取了脏数据了么

@BigBlackSheep
Copy link
Author

@BigBlackSheep BigBlackSheep commented Apr 22, 2019

@joyway66
Copy link

@joyway66 joyway66 commented May 22, 2019

先删除缓存,更新数据库,预计数据库更新需要时间t,然后在提交数据库更新t时间后再次删除缓存

@josephhao
Copy link

@josephhao josephhao commented May 22, 2019

@SJP-github
Copy link

@SJP-github SJP-github commented Sep 2, 2019

所以作者说先删缓存,再更新数据,然后用内存队列保证串行化,等数据库更新完再去读库就没有这么多幺蛾子了。还有同学提到的用读写锁。读写锁底层用的AQS其实也是用内存队列做的,保证顺序

@yanglbme yanglbme changed the title 缓存与数据库的双写一致性 缓存与数据库的双写一致性 | Consistency between redis cache and sql database Sep 19, 2019
@MissHuZeQuan
Copy link

@MissHuZeQuan MissHuZeQuan commented Dec 17, 2019

个人觉得缓存双删比较简单靠谱,而且编码不是那么复杂

@nethibernate
Copy link

@nethibernate nethibernate commented Jan 7, 2020

作者写的其实比较清楚了。

不管先删除缓存,还是先更新数据库,都会出现数据不一致问题,因为这本身就不是一个原子操作。只是作者提到,如果不在高并发的情况下,这种现象出现的可能性极低(但不代表不存在)。

其实原子操作相对于非原子操作来说,本来就是一个极其耗性能的东西,因为原子操作需要的是排他的串行操作,根据阿姆达尔定律就可以证明。

@geekhoon
Copy link

@geekhoon geekhoon commented Jan 14, 2020

高并发下保证绝对的一致,就需要用到内存队列做异步串行化。非高并发场景,双删策略基本满足了。如果特别要求强一致性,并不适合放在redis

@BigBlackSheep
Copy link
Author

@BigBlackSheep BigBlackSheep commented Jan 15, 2020

看到各位回答很多人说:内存队列做串行化.可是这样就降低了效率.这里提供一个新思路就是.先更新数据库再删除缓存.之后使用MQ延迟队列(可以延迟个50-100毫秒)去做异步再删除.

@geekhoon
Copy link

@geekhoon geekhoon commented Jan 15, 2020

看到各位回答很多人说:内存队列做串行化.可是这样就降低了效率.这里提供一个新思路就是.先更新数据库再删除缓存.之后使用MQ延迟队列(可以延迟个50-100毫秒)去做异步再删除.

首先我认为肯定要先删缓存,再更新数据库,这二者的顺序是不能换的,否则更新完成,缓存未删除,查到的数据那势必不一致。
使用MQ异步删除,并不能保证一致性
内存队列做串行化是考虑具体场景的,并不适用所有情况。有强一致性要求的才考虑这种做法。否则双删即可

@BigBlackSheep
Copy link
Author

@BigBlackSheep BigBlackSheep commented Jan 15, 2020

看到各位回答很多人说:内存队列做串行化.可是这样就降低了效率.这里提供一个新思路就是.先更新数据库再删除缓存.之后使用MQ延迟队列(可以延迟个50-100毫秒)去做异步再删除.

首先我认为肯定要先删缓存,再更新数据库,这二者的顺序是不能换的,否则更新完成,缓存未删除,查到的数据那势必不一致。
使用MQ异步删除,并不能保证一致性
内存队列做串行化是考虑具体场景的,并不适用所有情况。有强一致性要求的才考虑这种做法。否则双删即可

对 这个是我说错了 要先删缓存,再更新数据库,这二者的顺序是不能换的.使用MQ做异步删除为什么能保证一致性呢 可以展开说一下嘛.

@geekhoon
Copy link

@geekhoon geekhoon commented Jan 15, 2020

看到各位回答很多人说:内存队列做串行化.可是这样就降低了效率.这里提供一个新思路就是.先更新数据库再删除缓存.之后使用MQ延迟队列(可以延迟个50-100毫秒)去做异步再删除.

首先我认为肯定要先删缓存,再更新数据库,这二者的顺序是不能换的,否则更新完成,缓存未删除,查到的数据那势必不一致。
使用MQ异步删除,并不能保证一致性
内存队列做串行化是考虑具体场景的,并不适用所有情况。有强一致性要求的才考虑这种做法。否则双删即可

对 这个是我说错了 要先删缓存,再更新数据库,这二者的顺序是不能换的.使用MQ做异步删除为什么能保证一致性呢 可以展开说一下嘛.

比如说你先删缓存,现在去修改数据库,修改的过程中有查询操作去查了数据库,又把旧数据添加到了缓存,你现在数据库更新完了,用mq去延迟删除缓存,那这个延迟期间,所有的查询查的还是旧数据

@MissHuZeQuan
Copy link

@MissHuZeQuan MissHuZeQuan commented Jan 15, 2020

@nethibernate
Copy link

@nethibernate nethibernate commented Jan 16, 2020

保证了最终一致性,你要是要求强一致性,那在考虑别的方法

------------------------------------------------------------------ 发件人:geekhoon notifications@github.com 发送时间:2020年1月15日(星期三) 16:44 收件人:doocs/advanced-java advanced-java@noreply.github.com 抄 送:MissHuZeQuan huzequan1109@dingtalk.com; Comment comment@noreply.github.com 主 题:Re: [doocs/advanced-java] 缓存与数据库的双写一致性 | Consistency between redis cache and sql database (#54) 看到各位回答很多人说:内存队列做串行化.可是这样就降低了效率.这里提供一个新思路就是.先更新数据库再删除缓存.之后使用MQ延迟队列(可以延迟个50-100毫秒)去做异步再删除. 首先我认为肯定要先删缓存,再更新数据库,这二者的顺序是不能换的,否则更新完成,缓存未删除,查到的数据那势必不一致。 使用MQ异步删除,并不能保证一致性 内存队列做串行化是考虑具体场景的,并不适用所有情况。有强一致性要求的才考虑这种做法。否则双删即可 对 这个是我说错了 要先删缓存,再更新数据库,这二者的顺序是不能换的.使用MQ做异步删除为什么能保证一致性呢 可以展开说一下嘛. 比如说你先删缓存,现在去修改数据库,修改的过程中有查询操作去查了数据库,又把旧数据添加到了缓存,你现在数据库更新完了,用mq去延迟删除缓存,那这个延迟期间,所有的查询查的还是旧数据 — You are receiving this because you commented. Reply to this email directly, view it on GitHub, or unsubscribe.

这个和保证最终一致性没有关系。这个方案是在非高并发的情况下,“赌”它不会出现不一致。如果“赌”失败了,就一定会不一致!

@smile2coder
Copy link

@smile2coder smile2coder commented Mar 5, 2020

请问一下各位,先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。什么情况下会出现删除缓存失败?如果是redis宕机,那程序会报错,事务会回滚;如果是删除错了缓存,那是代码的问题了,请不吝赐教

@scoful
Copy link

@scoful scoful commented Apr 6, 2020

类似这种多个操作,又要原子保证的,是不是直接加redis锁就可以了呢?
也不用考虑是要先删库还是先删redis。
不用自己再维护一个内存序列,
还不用去考虑用nginx去路由到同一个实例上,复杂度大大降低了。
相当于事务,要么都成功,要么都失败。

@kaitezhan
Copy link

@kaitezhan kaitezhan commented Apr 16, 2020

作者最后方案是放入队列,都放入队列,那怎么响应给客户端呢?

yanglbme added a commit that referenced this issue Apr 20, 2020
@zpfang
Copy link

@zpfang zpfang commented Apr 26, 2020

如果是在读是高并发,而写是少量的情况下,删除更新数据库之后,
是否可以做一个redis的乐观更新,
更新数据库之后主动更新缓存信息,
更新缓存之后查询缓存数据是否正确,否则再次更新。
如果是读和写都是高并发的,写数据库操作是否可以改成MQ去做持久化
刚写完发现:https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-cas.md 这里也介绍了redis的乐观锁机制

@Lan1995
Copy link

@Lan1995 Lan1995 commented May 5, 2020

请问一下各位,先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。什么情况下会出现删除缓存失败?如果是redis宕机,那程序会报错,事务会回滚;如果是删除错了缓存,那是代码的问题了,请不

请问一下各位,先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。什么情况下会出现删除缓存失败?如果是redis宕机,那程序会报错,事务会回滚;如果是删除错了缓存,那是代码的问题了,请不吝赐教

redis是先把数据存缓存的,然后通过AOF一秒刷一次磁盘,如果这时候宕机会丢掉这一秒的数据。然后根据RDB+AOF恢复之后,数据自然是不一致的

@limingbonc
Copy link

@limingbonc limingbonc commented May 7, 2020

感觉没有那么绝对最好的方案,适合自己项目的就行
1.要是读操作并发高,先删在没修改数据库前,很多请求读取的都是旧数据。
2.写操作多的话,适合先改数据库后删除缓存。

@wengshenghua
Copy link

@wengshenghua wengshenghua commented May 22, 2020

更新的时候先更新数据库,再删除缓存,如果缓存删除失败直接报错回滚数据库更新最简单实现

@wengshenghua
Copy link

@wengshenghua wengshenghua commented May 22, 2020

请问一下各位,先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。什么情况下会出现删除缓存失败?如果是redis宕机,那程序会报错,事务会回滚;如果是删除错了缓存,那是代码的问题了,请不

请问一下各位,先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。什么情况下会出现删除缓存失败?如果是redis宕机,那程序会报错,事务会回滚;如果是删除错了缓存,那是代码的问题了,请不吝赐教

redis是先把数据存缓存的,然后通过AOF一秒刷一次磁盘,如果这时候宕机会丢掉这一秒的数据。然后根据RDB+AOF恢复之后,数据自然是不一致的

先删除缓存,再更新数据库一样会遇到你这个问题,丢失的这一秒,有可能就会丢失你的删除操作把

@Hunter-Chen
Copy link

@Hunter-Chen Hunter-Chen commented Jun 5, 2020

请问一下各位,先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。什么情况下会出现删除缓存失败?如果是redis宕机,那程序会报错,事务会回滚;如果是删除错了缓存,那是代码的问题了,请不吝赐教

redis宕机并不一定会报错. 比如说用的redis cluster, 发生了分裂, 你是分裂后连接到少的那部分redis机器. 你认为你删除了缓存,其实并没有.

@Hunter-Chen
Copy link

@Hunter-Chen Hunter-Chen commented Jun 5, 2020

更新
``

更新的时候先更新数据库,再删除缓存,如果缓存删除失败直接报错回滚数据库更新最简单实现

更新完数据库之后还没来得及删除缓存,机子挂掉了怎么办?

@wengshenghua
Copy link

@wengshenghua wengshenghua commented Jun 6, 2020

?

更新
``

更新的时候先更新数据库,再删除缓存,如果缓存删除失败直接报错回滚数据库更新最简单实现

更新完数据库之后还没来得及删除缓存,机子挂掉了怎么办?

更新
``

更新的时候先更新数据库,再删除缓存,如果缓存删除失败直接报错回滚数据库更新最简单实现

更新完数据库之后还没来得及删除缓存,机子挂掉了怎么办?

为什么不把删除缓存 放到更新数据的事务里面?

@wengshenghua
Copy link

@wengshenghua wengshenghua commented Jun 6, 2020

请问一下各位,先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。什么情况下会出现删除缓存失败?如果是redis宕机,那程序会报错,事务会回滚;如果是删除错了缓存,那是代码的问题了,请不吝赐教

redis宕机并不一定会报错. 比如说用的redis cluster, 发生了分裂, 你是分裂后连接到少的那部分redis机器. 你认为你删除了缓存,其实并没有.

那照你这么说,就算先删除缓存,再更新数据库也没有任何意义了。因为"你认为你删除了缓存,其实并没有"

@TableofContents
Copy link

@TableofContents TableofContents commented Jun 6, 2020

删除缓存的一瞬间,流量瞬间进来后,redis缓存已经失去了意义了吧。
就不能删除,直接采用更新,先插入mysql,再监听binlog日志,放入kafka中,然后更新redis缓存,只要缓存最终一致性就行了啊。

强一致性,怎么可能实现呢? 我发现很多人面试官都说先删除缓存,然后请求来了发现没缓存,再读取数据库,再插入缓存?

那假如来了1000并发,1000个请求,都发现没有缓存,然后1000个请求,都去后台mysql了。难道再加个分布式锁吗?只有一个线程可以去后台?

@1305420113
Copy link

@1305420113 1305420113 commented Jun 6, 2020

删除缓存的一瞬间,流量瞬间进来后,redis缓存已经失去了意义了吧。
就不能删除,直接采用更新,先插入mysql,再监听binlog日志,放入kafka中,然后更新redis缓存,只要缓存最终一致性就行了啊。
强一致性,怎么可能实现呢? 我发现很多人面试官都说先删除缓存,然后请求来了发现没缓存,再
读取数据库,再插入缓存?
那假如来了1000并发,1000个请求,都发现没有缓存,然后1000个请求,都去后台mysql了。难道再加个分布式锁吗?只有一个线程可以去后台?

多个线程之间串行处理

@1305420113
Copy link

@1305420113 1305420113 commented Jun 6, 2020

删除缓存的一瞬间,流量瞬间进来后,redis缓存已经失去了意义了吧。
就不能删除,直接采用更新,先插入mysql,再监听binlog日志,放入kafka中,然后更新redis缓存,只要缓存最终一致性就行了啊。
强一致性,怎么可能实现呢? 我发现很多人面试官都说先删除缓存,然后请求来了发现没缓存,再
读取数据库,再插入缓存?
那假如来了1000并发,1000个请求,都发现没有缓存,然后1000个请求,都去后台mysql了。难道再加个分布式锁吗?只有一个线程可以去后台?

在你说的情况下更新radis缓存时,如果更新缓存失败了,mysql中是新数据,radis是旧数据,数据不一致,这其实是先操作mysql后操作缓存的弊端(后删除缓存也一样)。
先删除缓存,后更新mysql的话,mysql更新失败的话,缓存中是空的,mysql是旧数据,后面请求过来读数据库中的旧数据,然后更新到缓存中,数据是一致的

@TableofContents
Copy link

@TableofContents TableofContents commented Jun 7, 2020

删除缓存的一瞬间,流量瞬间进来后,redis缓存已经失去了意义了吧。
就不能删除,直接采用更新,先插入mysql,再监听binlog日志,放入kafka中,然后更新redis缓存,只要缓存最终一致性就行了啊。
强一致性,怎么可能实现呢? 我发现很多人面试官都说先删除缓存,然后请求来了发现没缓存,再
读取数据库,再插入缓存?
那假如来了1000并发,1000个请求,都发现没有缓存,然后1000个请求,都去后台mysql了。难道再加个分布式锁吗?只有一个线程可以去后台?

在你说的情况下更新radis缓存时,如果更新缓存失败了,mysql中是新数据,radis是旧数据,数据不一致,这其实是先操作mysql后操作缓存的弊端(后删除缓存也一样)。
先删除缓存,后更新mysql的话,mysql更新失败的话,缓存中是空的,mysql是旧数据,后面请求过来读数据库中的旧数据,然后更新到缓存中,数据是一致的

那你们继续用作者提供的这几种方式吧,目前我们用的不是。
我也悟不出来你们这么做的好处,我的能力太菜了。虚心向更为大佬学习。

@wumucheng
Copy link

@wumucheng wumucheng commented Sep 29, 2020

不是应该不能删除缓存吗,如果是热点数据,不是会造成缓存击穿吗?还是把更新数据库和更新redis数据做成事务好一些

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.