前言
做C端相关业务,目前主流的关系型数据库在高并发的查询请求场景下,很难做到低延迟的高并发,甚至有可能被打挂。因此引入缓存中间件是一个常见的解决方案,但如何保证缓存与数据库的一致性,便成为了一个棘手的问题,这次我们拿常见的Mysql和Redis举例。
正文
保持缓存和数据库的一致性,最简单的做法就是直接在业务中去双写或删除保持一致性;如果要跟业务解耦,就要采用订阅binlog或者定时刷新的方式完成。
业务耦合的一致性方案
业务中耦合更新缓存
其中一种常见的方案是更新缓存,当数据变更时更新对应缓存数据到缓存中,该行为可以在同一个业务中也可用消息中间件解耦。当查询请求来时,就直接查询缓存返回数据,如下图:
弊端:
当出现并发数据变更时,如A、B对同一行数据进行变更,A先变更完成,接着B变更完成,B更新缓存,A更新缓存。数据库中的数据是B的数据,但是缓存中却是A的数据。
与业务耦合不可取,增加业务复杂性以及依赖的中间件。
业务中耦合删除缓存
其实无论是前置更新还是后置更新缓存,在一些并发的极端场景都会出现缓存不一致的问题(思考一下前置更新),所以一个可以走的路子是删除缓存。当数据变更后,删除对应key的缓存,缓存的更新由C端业务来保证,如下图:
使用删除缓存的方式,不管数据变更这里的修改并发有多高,都能保证最终C端查询处放入的缓存是正确的,跟数据库保持一致的。
弊端:
- 对于删除缓存来说,一个明显的弊端在于当第二步删除缓存时,如果数据查询这里保持较高的流量,那么就会有一大批的请求透过缓存打到db中,还是有极大的风险;
- 依旧是未与业务解耦。
业务解耦的一致性方案
对于Mysql来说,如果要做到与业务解耦,首先想到的肯定是binlog,因为binlog毕竟记录了全量真实落库的数据,将自己伪装成一个从节点,订阅binlog就能完成业务上的解耦,如下图:
如上图说示,此时的缓存被视为db中的一份完整的镜像数据,但是仍旧无法避免并发场景下多线程消费binlog导致的缓存不一致问题。
引入定时任务与数据校验机制的binlog缓存更新机制
那如何能解决binlog监听时的数据乱序问题,并且有一定的容错机制保证缓存的最终一致性?
如下图所示,我们基于之前的binlog监听方案新增了两处改动。
- 通过监听消费binlog时记录binlog产生时间,并且每次对比binlog时间解决多线程消费的情况下,缓存和db数据最终不一致的问题;
- 由于每次update都会产生binlog,那么对于配置表的全表update(非业务字段,如set update_time=now())即可完成数据全量更新同步至缓存,通过定时任务定时触发,这样就能给业务带来更高的容错性。
结语
没有完美的方案,只有最适合自己业务的方案,如果当前基建较弱并且工期较短,那么跟业务耦合的缓存删除方案就基本满足大多数场景;如果工期满足,则可以探索一些更好的一致性方案,甚至引入分布式事务来保证缓存数据的更新,也未尝不可。
最近的一些业务需求,与缓存打交道较多,后续会持续更新热点key、大key的一些方案分享。