redis 缓存一致性

170 阅读3分钟

9d78be4b0ac44a52c9ce629e8f63bf2d.png

1. 一致性

1.1 强一致性

系统写入什么,读出来就是什么,对性能影响大

1.2 弱一致性

系统写入后,不保证一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态

1.3 最终一致性

弱一致性的特例,系统会保证在一定时间内,能够达到一个数据一致的状态

2. 缓存模式

2.1 Cache-Aside Pattern 旁路缓存模式

image.png

image.png

2.2 Read/Write -through 读写穿透模式

7b8ca8721e5f7da70376875f14e96cfb.png

504b11c77f6a8609f09f23fac659c4da.png

Read-Through实际只是在Cache-Aside 之上进行了一层封装,它会让程序代码变得更简洁,同时也减少数据源上的负载。写入时同步更新缓存和数据的

2.3 Write-behind 异步缓存写入

fb33e93d464712d06b8b7c1b5b771ba8.png

Write-Behind 则是只更新缓存,不直接更新数据库,通过批量异步 的方式来更新数据库。

它适合频繁写的场景,MySQL的InnoDB Buffer Pool机制 就使用到这种模式。

3. 更新缓存还是删除缓存

3.1 更新缓存

69fe5712c096ade14e2e3b9b1270fcf2.png

数据不一致情况:

  1. 线程A先发起一个写操作,第一步先更新数据库
  2. 线程B再发起一个写操作,第二步更新了数据库
  3. 由于网络等原因,线程B先更新了缓存
  4. 线程A更新缓存。

3.2 删除缓存

3.2.1 先删后写

f5f48a95c05200d1ce561868be682ed3.png

数据不一致:

  1. 线程A发起一个写操作,第一步del cache
  2. 此时线程B发起一个读操作,cache miss
  3. 线程B继续读DB,读出来一个老数据
  4. 然后线程B把老数据设置入cache
  5. 线程A写入DB最新的数据

3.2.2 先写后删

数据不一致:

  1. 写入数据库成功
  2. 删除缓存失败, 其他线程读到老缓存

4. 缓存一致性解决方案

4.1 延时双删

08dbb613707697df7b58aba04ef58707.png

  1. 先删除缓存
  2. 再更新数据库
  3. 异步 休眠一会(比如1秒),再次删除缓存

问题1: 直接更新数据库后等事务提交了删缓存不就行了?(为什么要延时?)

spring中有事务提交回调,在回调中删除redis缓存, 这种在单机情况下,是没有问题的,但是在mysql主从架构下,会产生问题, 当写入mysql主库时,由于主从同步,从库数据时旧数据,然而redis缓存已被删除,请求打到redis,发现无数据,请求mysql从库,读取到旧数据放入redis中,此时数据不一致产生

A线程第一次删之后,B线程读到了旧数据,此时进入GC,然后A线程进行第二次删除,然后B线程设置旧缓存

所以,延时时间 取决于1. mysql主从同步时间 2. GC时间 ,一般设置为2s左右

问题2: 为什么要删两次?第一次不删行不行?

第一次可以不删,最终也是一致的,但是不一致时间会变长

image.png

4.2 删除缓存重试机制

当第二部缓存删除失败了,数据就会不一致,那么需要引入重试机制

e7086f8b018dcb7ea80d43db054dfccc.png

4.3 读binlog异步删除

9fe12b61c540cd6435a45d1603e5f727.png

可以使用阿里的canal将binlog日志采集发送到MQ队列里面

然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性