单节点场景下,数据库(MySQL)与缓存(Redis)的一致性分析

为了减轻数据库的负担,使用redis作为缓存。查询时先查询redis,redis存在则直接返回,不用访问数据库,缓解了数据库的压力。

数据存在两个地方:redis、数据库。如何保证这两个地方的数据的一致性?

读场景

  1. redis中存在数据,直接返回redis中的数据。
  2. redis中不存在数据,查询数据库后,保存数据到redis。

数据只查询,不修改。缓存到redis中的数据与数据库保持一致,不存在一致性问题。

写场景

  1. redis中不存在数据,则直接更新数据库中的数据。
  2. 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:先更新数据库,再删除缓存的策略出现一致性问题的严重程度较低。只会出现更新时的短时数据不一致,或者极小概率下出现不一致的情况。如果对一致性要求不是很高,可以采用这种方案。

如果对于数据一致性要求极高,提供两种思路:

  1. 先删除缓存,再更新数据库。采用异步更新缓存
    A线程先删除缓存,B线程在读取时,从数据库读取数据,但不写入缓存。
    A线程通过订阅数据库的binlog,写入redis缓存。
    缺点是:在未完成更新时,会出现大量请求直接访问数据库,容易出现数据库崩溃。

  2. 先删除缓存,再更新数据库。采用延迟双删策略
    A线程删除缓存,B线程读取数据库数据并写入缓存。
    A线程完成更新后,再把缓存中的数据删除。

posted @ 2024-03-20 16:24  ︶ㄣ演戲ㄣ  阅读(14)  评论(0编辑  收藏  举报  来源