单节点场景下,数据库(MySQL)与缓存(Redis)的一致性分析
为了减轻数据库的负担,使用redis作为缓存。查询时先查询redis,redis存在则直接返回,不用访问数据库,缓解了数据库的压力。
数据存在两个地方:redis、数据库。如何保证这两个地方的数据的一致性?
读场景
- redis中存在数据,直接返回redis中的数据。
- redis中不存在数据,查询数据库后,保存数据到redis。
数据只查询,不修改。缓存到redis中的数据与数据库保持一致,不存在一致性问题。
写场景
- redis中不存在数据,则直接更新数据库中的数据。
- redis中存在数据,则需要同时更新redis和数据库的数据,保证它们的一致性。
同时更新redis和数据库的数据
问题1: 由于有数据库兜底,redis中不是必须要有数据的。那么在更新时,是删除redis中的数据还是修改redis中的数据?
问题2: 先操作数据库还是先操作redis?
方案1 | 方案2 | 方案3 | 方案4 |
---|---|---|---|
先操作数据库,删除redis | 先操作redis,删除redis | 先操作数据库,更新redis | 先操作redis,更新redis |
方案1:先更新数据库,再删除redis
更新时,先更新数据库的数据,然后删除redis中的旧数据。
读写场景:
场景一:缓存X,数据库X。A更新时,B读取
A更新数据库 数据库A,缓存X
B读取缓存 读取缓存X
A删除缓存 数据库A,无缓存
其他线程读 数据库A,缓存A
结果:出现短时数据不一致问题,随后数据保持一致
场景二:无缓存,数据库X。A更新时,B读取
此时A、B操作数据库,A写B读,根据MySQL的innodb引擎默认隔离级别是可重复读,B读取到的是旧数据。
如果因为网络堵塞等原因,导致A先返回,之后B再返回数据,把旧数据写入缓存。此时出现数据不一致问题。
如果B先返回旧数据到缓存,A再返回,把旧数据缓存删除,此时会出现短时数据一致性问题。
结果:出现短时数据不一致。或者出现数据不一致(概率低,A是写操作,B是读操作,B的速度要快得多)
写写场景:缓存X,数据库X。A更新时,B更新
A更新数据库 数据库A,缓存X
B更新数据库 数据库B,缓存X
A删除缓存 数据库B,无缓存
B删除缓存 数据库B,无缓存
其他线程读 数据库B,缓存B
结果:写写场景没有一致性问题
综合分析,该方案会小概率出现数据不一致问题,会出现短时数据不一致问题。
方案2:先删除redis,再更新数据库
更新时,先操作redis,删除redis中的数据,然后更新数据库。
读写场景:
场景1: 缓存X,数据库X。A更新时,B读取
A删除缓存 数据库X,无缓存
B读取 读取X,缓存X
A更新数据库 数据库A,缓存X
其他线程读取 读取缓存X
结果:出现数据一致性问题。
写写场景:
场景1:缓存X,数据库X。A更新时,B更新
A删除缓存 数据库X,无缓存
B要删除缓存,但无缓存 数据库X,无缓存
A更新数据库 数据库A,无缓存 (有无可能,B先更新数据库)
B更新数据库 数据库B,无缓存
其他线程读 数据库B,缓存B
结果:能保证一致性
综合分析,读写时会出现数据一致性问题。
方案3:先更新数据库,再更新redis
更新时,先更新数据库的数据,然后把数据更新到redis。
读写场景:
场景1:数据库X,缓存X。A写时,B读
A更新数据库 数据库A,缓存X
B读取 读取缓存X
A更新缓存 数据库A,缓存A
其他线程读 读取缓存A
结果:短时不一致
场景2:数据库X,无缓存。A写时,B读
A更新数据库 数据库A,无缓存
B读取 读取数据库A,写入缓存A
A更新缓存 数据库A,缓存A
结果:无一致性问题。
写写场景:
场景1:数据库X,缓存X。A写时,B写
A更新数据库 数据库A,缓存X
B更新数据库 数据库B,缓存X
B更新缓存 数据库B,缓存B (由于网络堵塞等原因,B先把数据返回给redis)
A更新缓存 数据库B,缓存A
其他线程读 读取缓存A
结果:会出现一致性问题
综合分析,会出现一致性问题。
方案4:先更新redis,再更新数据库
先在redis中更新数据,然后同步数据到数据库。
redis中的数据很容易丢失(过期淘汰,内存淘汰),在更新完redis后,如果数据丢失,未同步到数据库。则造成数据不一致问题。
如果能够保证redis的数据不丢失,进行分析:
读写场景:√
A更新缓存 缓存A
B读取缓存
A写入数据库 数据库A
最终缓存A,数据库A
写写场景:×
A更新缓存 缓存A
B更新缓存 缓存B (A由于网络阻塞,B先进行操作数据库)
B更新数据库 数据库B
A更新数据库 数据库A
最终缓存B,数据库A
可以看到,会出现缓存与数据库不一致问题。
总结
综合来看,四种方案都有一定概率出现缓存与数据库的不一致问题。
对于更新缓存的策略:
当更新的逻辑比较复杂时,缓存需要多次与数据库交互,把信息传入缓存,消耗资源较大,而去更新后的数据不一定被查询使用。
同比之下方案1:先更新数据库,再删除缓存的策略出现一致性问题的严重程度较低。只会出现更新时的短时数据不一致,或者极小概率下出现不一致的情况。如果对一致性要求不是很高,可以采用这种方案。
如果对于数据一致性要求极高,提供两种思路:
-
先删除缓存,再更新数据库。采用异步更新缓存
A线程先删除缓存,B线程在读取时,从数据库读取数据,但不写入缓存。
A线程通过订阅数据库的binlog,写入redis缓存。
缺点是:在未完成更新时,会出现大量请求直接访问数据库,容易出现数据库崩溃。 -
先删除缓存,再更新数据库。采用延迟双删策略
A线程删除缓存,B线程读取数据库数据并写入缓存。
A线程完成更新后,再把缓存中的数据删除。