Redis和MySQL分布式双写一致性

前言

一个MySQL服务和一个Redis服务,用户的数据存储持久化在MySQL中,缓存在Redis中,有请求的时候从Redis中获取缓存的用户数据,有修改则同时修改MySQL和Redis中的数据。现在问题是:不论是先保存到MySQL还是Redis,都面临着此成功彼失败的情况,那么如何保证MySQL与Redis中的数据一致?

数据一致性

数据一致性主要出现在使用不同存储组件的情况下,存储组件之间无法直接通信,所以不能相互之间实现数据交换,但是使用第三方来单独操作各存储组建时,有极大的可能造成各存储组件之间数据不一致。

数据一致性分为强一致性和最终一致性,强一致性的情况是不论何时访问哪一个存储组件,所得到的数据都是一样的,最终一致性的情况是可以在某短时间内访问不同存储组件所得到的数据允许不一样。我们实际的生产开发中,基本都是遵循最终一致性。

MySQL和Redis一致性

Redis一般用于存储热点数据,MySQL存储所有数据。但是在更新缓存这方面有多种方案:

  • 先删除缓存,再更新数据库
  • 先更新缓存,再更新数据库
  • 先更新数据库,在更新缓存
  • 先更新数据库,再删除缓存
  • 先更新数据库,再给缓存设置过期时间
  • 使用Canal中间件

先删除缓存,再更新数据库(不可取)

1
2
3
def method():
deleteRedis()
updateDB()

第一步先删除缓存,第二步再更新数据库,如果在第一步之后,其他线程发生了数据库读操作,然后读到的旧数据又set到了Redis中,然后第二步执行,最终Redis和MySQL中的数据出现了不一致

先更新缓存,再更新数据库(不可取)

1
2
3
def method():
updateRedis()
updateDB()

第一步先更新缓存,第二步更新数据库,如果更新Redis成功,但是更新数据库失败,则两者的数据又出现了不一致

先更新数据库,在更新缓存(不可取)

1
2
3
def method():
updateDB()
updateRedis()

两个线程同时对一条数据进行操作,在线程B先于线程A更新缓存成功,则造成了缓存中的数据低于MySQL一个版本

####先更新数据库,再删除缓存(可取)

1
2
3
def method():
updateDB()
deleteRedis()

两个线程同时操作一条数据,甭管哪一个先操作成功,最终都会删除掉缓存中的数据,然后由查询将最新数据set到缓存中,但是在并发量大的情况下,由于缓存被删除了两次,可能会造成缓存击穿

先更新数据库,再给缓存设置过期时间(推荐)

1
2
3
def method():
updateDB()
expireRedis()

更新完数据库之后,并不立即删除缓存,而是给缓存设置一个过期时间,由缓存自行过期,使用这个方案的前提是能允许数据可以很短时间内的不一致,并且推荐

使用Canal中间件(可取)

Canal是阿里巴巴开源的一款数据库同步工具,他可以将自己伪装成一个MySQL Slave节点,将MySQL的binlog文件同步到Canal,然后由Canal进行下一步处理,比如存入MQ、Redis等,但是可能会存在些许延迟。后续讨论

强一致性

如果想要求数据强一致性,就只用MySQL,不要用Redis,以免出现不一致的情况