Redis与DB双写一致性
约 1393 字大约 5 分钟
2025-01-22
1. 一致性
一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。
- 强一致性: 这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大;
- 弱一致性: 这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态
- 最终一致性: 最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型
2. 缓存操作
- Cache-Aside Pattern:读的时候,先读缓存,缓存命中的话,直接返回数据; 缓存没有命中的话,就去读数据库,从数据库取出数据,放入缓存后,同时返回响应。
- Read-Through/Write through:从缓存读取数据,读到直接返回,如果读取不到的话,从数据库加载,写入缓存后,再返回响应。
- Write behind:只更新缓存,不直接更新数据库,通过批量异步的方式来更新数据库
2.1 为什么删除缓存而不是更新缓存?
- 线程A先发起一个写操作,第一步先更新数据库;
- 线程B再发起一个写操作,第二步更新了数据库;
- 由于网络等原因,线程B先更新了缓存, 线程A更新缓存;
- 这时候,缓存保存的是A的数据(老数据),数据库保存的是B的数据(新数据),数据不一致了,脏数据出现啦。如果是删除缓存取代更新缓存则不会出现这个脏数据问题。
2.1 为什么先操作数据库而不是先缓存?
- 线程A发起一个写操作,第一步del cache
- 此时线程B发起一个读操作,cache miss
- 线程B继续读DB,读出来一个老数据
- 然后线程B把老数据设置入cache
- 线程A写入DB最新的数据
3. 强一致性还是弱一致性
CAP理论,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。
CAP理论作为分布式系统的基础理论,它描述的是一个分布式系统在以下三个特性中:
- 一致性(Consistency)
- 可用性(Availability)
- 分区容错性(Partition tolerance)
最多满足其中的两个特性。也就是下图所描述的。分布式系统要么满足CA,要么CP,要么AP。无法同时满足CAP。 如果需要数据库和缓存数据保持强一致,就不适合使用缓存。通过一些方案优化处理,是可以保证弱一致性,最终一致性的。
4. 3种方案保证数据库与缓存的一致性
- 延时双删策略
- 删除缓存重试机制
- 读取biglog异步删除缓存
4.1 缓存延时双删
- 先删除缓存
- 再更新数据库
- 休眠一会(比如1秒),再次删除缓存。
TimeUnit.SECONDS.sleep(2);
4.2 删除缓存重试机制
不管是延时双删还是Cache-Aside的先操作数据库再删除缓存,如果第二步的删除缓存失败呢? 删除失败就多删除几次呀,保证删除缓存成功呀~ 所以可以引入删除缓存重试机制
删除缓存重试机制的大致步骤:
- 写请求更新数据库
- 缓存因为某些原因,删除失败
- 把删除失败的key放到消息队列
- 消费消息队列的消息,获取要删除的key
- 重试删除缓存操作
4.3 同步biglog异步删除缓存
使用mq保证删除成功
5. 缓存与数据的一致性的保障策略总结
在分布式系统中,缓存和数据库同时存在时,如果有写操作的时候,「先操作数据库,再操作缓存」。如下:
- 读取缓存中是否有相关数据;
- 如果缓存中有相关数据value,则返回;
- 如果缓存中没有相关数据,则从数据库读取相关数据放入缓存中key->value,再返回;
- 如果有更新数据,则先更新数据库,再删除缓存;
- 为了保证第四步删除缓存成功,使用binlog异步删除;
- 如果是主从数据库,binglog取自于从库;
- 如果是一主多从,每个从库都要采集binlog,然后消费端收到最后一台binlog数据才删除缓存,或者为了简单,收到一次更新log,删除一次缓存;