背景
现在主流的缓存加速都用的redis, 但是redis不负责持久化, 往往需要mysql或者其它持久化数据库来配合, 这就带来一个问题:如何保持两边数据的一致性。
设计
-
容易想到的是更新数据库的同时再去删除redis里面的数据, 但是这是两步操作,需要保证事务性,如果引入事务必然导致性能的下降,那么有没其它方案
- 是不是可以先删除redis再去更新数据库, 下次有业务需要读的时候再去加载数据库。这样是不是就保证了缓存和数据库的一致呢?
-
试想删除redis再更新数据库, 在这两步操作中间又有大量查询的情况下,那么redis是不是还是需要加载老的数据, 也最终是引用的老数据
-
考虑并发的情况呢,假如有两个人都需要更新这个key,那么他们操作都是先删除redis里面对应的key,再去更新数据库, 这个时候都同时发起删除结束之后,再一先一后的完成数据库更新, 那么这数据库操作的中间有可能就完成了redis的更新,那么就必然要丢失第二次更新数据对应的删除(删除是为了查询最新的更新)。
- 考虑到2和3的情况, 仍然面临缓存数据不太准确,主要是因为数据在redis存在之后就不再会去数据库更新, 如果每次查询redis都去数据库对比数据是否一致,也势必会影响性能。那能否通过版本号来解决, 比如删除的时候并不是真的删除,而是记录两个字断:cur_version, need_version, 分别表示当前的版本, 以及需要更新到的版本, 通过一个自增id来达到。这样每次获取redis数据的时候都比较下这两个字断来判断是否需要去数据库里面更新。
-
通过版本号来解决会不会出现这样一个极端, 版本号在redis更新成功了, 但是数据库没有更新成功,会不会导致不断的去数据库查询?
- 可以通过设置重试次数来解决
-
会不会出现版本号在redis更新成功了, 但是在后面由于redis服务器宕机, 又出现版本回退的情况呢。 比如redis集群里面是主从结构, 假如主挂了, 从被当选为主的情况,由于主从同步是异步的, 导致从还没最新的版本记录,那么这个时候会导致数据不一致。
- 采用binlog回放机制能缓解,但是不能最终解决这个问题。binlog回放是每有一次更新都会去redis查询,如果有这个key就做更新, 保证了最终一致性。这中间也出现aba的问题, 比如redis已经是最新数据了, 但是binlog比较慢, 更新到老的版本, 这种也可以通过版本号比较来解决。尽管binlog有以上优点,还是无法解决redis集群架构问题,因为是异步复制, 肯定会导致有些数据丢失。
总结
缓存一致性除了应用层要设计好之外还需要本身缓存的架构要支持数据的高可用和一致性, 在现有redis集群架构中目前还是无法完全做到, 但是业务层如果设计好, 还是可以去避免大多数不一致的问题的。
不过这里的思考略微不够全面, 待后面第二篇来进行分析。
网友评论