1. 一致性
1.1 强一致性
系统写入什么,读出来就是什么,对性能影响大
1.2 弱一致性
系统写入后,不保证一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态
1.3 最终一致性
弱一致性的特例,系统会保证在一定时间内,能够达到一个数据一致的状态
2. 缓存模式
2.1 Cache-Aside Pattern 旁路缓存模式
2.2 Read/Write -through 读写穿透模式
Read-Through实际只是在Cache-Aside 之上进行了一层封装,它会让程序代码变得更简洁,同时也减少数据源上的负载。写入时同步更新缓存和数据的
2.3 Write-behind 异步缓存写入
Write-Behind 则是只更新缓存,不直接更新数据库,通过批量异步 的方式来更新数据库。
它适合频繁写的场景,MySQL的InnoDB Buffer Pool机制 就使用到这种模式。
3. 更新缓存还是删除缓存
3.1 更新缓存
数据不一致情况:
- 线程A先发起一个写操作,第一步先更新数据库
- 线程B再发起一个写操作,第二步更新了数据库
- 由于网络等原因,线程B先更新了缓存
- 线程A更新缓存。
3.2 删除缓存
3.2.1 先删后写
数据不一致:
- 线程A发起一个写操作,第一步del cache
- 此时线程B发起一个读操作,cache miss
- 线程B继续读DB,读出来一个老数据
- 然后线程B把老数据设置入cache
- 线程A写入DB最新的数据
3.2.2 先写后删
数据不一致:
- 写入数据库成功
- 删除缓存失败, 其他线程读到老缓存
4. 缓存一致性解决方案
4.1 延时双删
- 先删除缓存
- 再更新数据库
- 异步 休眠一会(比如1秒),再次删除缓存
问题1: 直接更新数据库后等事务提交了删缓存不就行了?(为什么要延时?)
spring中有事务提交回调,在回调中删除redis缓存, 这种在单机情况下,是没有问题的,但是在mysql主从架构下,会产生问题, 当写入mysql主库时,由于主从同步,从库数据时旧数据,然而redis缓存已被删除,请求打到redis,发现无数据,请求mysql从库,读取到旧数据放入redis中,此时数据不一致产生
A线程第一次删之后,B线程读到了旧数据,此时进入GC,然后A线程进行第二次删除,然后B线程设置旧缓存
所以,延时时间 取决于1. mysql主从同步时间 2. GC时间 ,一般设置为2s左右
问题2: 为什么要删两次?第一次不删行不行?
第一次可以不删,最终也是一致的,但是不一致时间会变长
4.2 删除缓存重试机制
当第二部缓存删除失败了,数据就会不一致,那么需要引入重试机制
4.3 读binlog异步删除
可以使用阿里的canal将binlog日志采集发送到MQ队列里面
然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性