谈谈一致性
一致性是指数据保持一致,在分布式系统中,可以理解为多个节点中的数据是一致的。
- 强一致性:用户写入什么数据,就可以读出什么数据。这种一致性最符合用户的直觉,用户体验好,但实现起来往往对系统的性能影响最大。
- 弱一致性:在用户写入系统成功后,不承诺可以立即读出写入的数据,也不承诺多久数据可以达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。
- 最终一致性:最终一致性是弱一致性的一种特例,系统会保证在一定时间内,数据能够达到一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型。
集中式redis缓存的三个经典的缓存模式
缓存可以提升性能,缓解数据库压力,但是缓存也会造成数据不一致的问题。那么一般是如何使用缓存的呢?
- 旁路缓存模式,Cache-Aside Pattern
- 读写穿透,Read-Through/Write-Through Pattern
- 写流程,Write behind
Cache-Aside Pattern
旁路缓存模式的读流程:先判断是否命中缓存,未命中则查询数据库并更新缓存。
旁路缓存模式写流程:更新数据库,然后删除缓存。
Read-Through/Write-Through 读写穿透
在读写穿透模式中,服务端把缓存作为主要数据存储。应用程序跟数据库缓存交互,都是通过抽象缓存层完成的。
Read-Through读流程:
从缓存中读数据,读到直接返回
未读到则查询数据库,然后更新缓存,再返回结果。
这个流程和旁路缓存模式很像,只是在查询流程中增加了一层Cache-Provider,流程如下:
我认为这层封装目的是,让使用者可以避免将未命中的查库更新缓存的场景,反复编写;使使用者更简单的时候缓存模式。
Write-Through写流程:
当发生写请求时,也是由缓存抽象层完成数据源和缓存数据的更新,流程如下:
Write behind(异步缓存写入)
Write behind跟Read-Through/Write-Through有相似的地方,都是由Cache Provider来负责缓存和数据库读写。它两个又有很大的不同,读写穿透是同步更新缓存和数据库,异步写入缓存则是只更新缓存,不直接更新数据库,通过批量异步的方式来更新数据库。
这种方式下,缓存和数据库的一致性不强,对一致性要求高的系统要谨慎使用。
但是它适合频繁写的场景,MySQL的InnoDB Buffer Pool机制就使用到这种模式。
三种模式的比较
1、旁路缓存模式实现起来比较简单,但是需要维护两个数据存储:
- 一个是缓存
- 一个是数据库
2、读写穿透模式的写模式需要维护一个数据存储(缓存),实现起来要复杂一些。
3、异步写模式与读写穿透模式相似,只是异步写是异步的,读写穿透是同步的。
4、异步写的优点是直接操作内存速度快,多次操作可以合并为一次持久化到数据库。缺点是数据可能会丢失,例如系统断电。
Cache-Aside的问题
更新数据的时候,Cache-Aside是删除缓存呢?还是应该更新缓存呢?
有些小伙伴会问,为什么Cache-Aside写入的时候,是删除缓存而不是更新缓存呢?下面来看个例子:
操作的次序如下:
- 线程A先发起一个写请求,先更新了数据库
- 线程B又发起一个写请求,又更新了数据库
- 由于网络原因,线程B先更新了缓存,然后线程A又更新了缓存。
- 这个时候,缓存中保存A的数据就是旧数据(数据库中B更新的数据是新数据),数据库不一致了,出现脏数据了。如果是删除缓存就不会出现这个问题。
更新缓存还有两个劣势:
- 如果写入的缓存值,是经过复杂计算才得到的话。更新频率高的话,会浪费性能。
- 在写多读少的情况下,很多数据没有被读到,数据就又被更新了,也会浪费性能。