后台缓存是软件开发中一个非常有用的概念,数据库缓存是项目中不可避免的场景。缓存一致性的保证在采访中被反复问到。以下是针对不同需求选择合适的一致性解决方案的总结。缓存存储在什么速度上是有区别的。缓存是一种将低速存储的结果暂时保存在高速存储中的技术。如图所示,金字塔上方的存储可以作为下方存储的缓存。我们这次的讨论主要是针对数据库缓存的场景,将以redis作为mysql的缓存作为一个案例。为什么需要缓存存储?例如mysql通常支持完整的ACID特性。由于可靠性和耐用性等因素,性能普遍不高。高并发查询会给mysql带来压力,导致数据库系统不稳定。它也容易出现延误。根据局部性原则,80%的请求会落在20%的热点数据上。在读多写少的场景下,增加一层缓存对于提高系统吞吐量和健壮性是很有帮助的。问题中存储的数据可能会随着时间的推移发生变化,缓存中的数据会不一致。具体可以容忍的不一致时间需要具体业务具体分析,但通常的业务需要最终一致。Redis作为mysql的缓存在通常的开发模式下,mysql作为存储,redis作为缓存来加速和保护mysql。但是,当mysql数据更新时,redis是如何保持同步的。强一致性同步成本太高。如果追求强一致性,那就不用缓存了,直接用mysql就可以了。通常考虑的是最终一致性。方案一:通过key的过期时间,mysql更新时,redis不会更新。这种方法实现起来简单,但是不一致的时间会很长。如果读请求非常频繁,过期时间比较长,会产生大量的长期脏数据。优点:开发成本低,易于实施;管理成本低,出现问题的机会少。缺点:完全依赖过期时间,时间太短缓存会频繁失效,太长更新延迟时间长。方案二在方案一的基础上进行扩展,利用key的过期时间来覆盖底线。更新mysql的同时更新redis。优点:与方案一相比,更新延迟更小。缺点:如果mysql更新成功,但是redis更新失败,会退化为第一种方案;在高并发场景下,业务服务器需要同时连接mysql和redis。这样一来,连接资源成倍增加,容易造成连接过多的问题。方案三针对方案二同步写入redis进行了优化,增加了一个消息队列,将redis的更新操作交给kafka。可靠性由消息队列保证,然后构建一个消费者服务异步更新redis。优点:消息队列可以使用句柄,很多消息队列客户端还支持本地缓存发送,有效解决了方案2中连接过多的问题;使用消息队列实现逻辑解耦;消息队列本身是可靠的,通过手动提交等方式,至少可以消费到redis一次。不足:还是解决不了时序问题,如果多个业务服务器处理同一行数据的两次请求,比如a=1;a=5;,如果mysql中第一个先执行,而进入kafka的顺序是第二个先执行,所以数据会不一致。引入了消息队列,同时需要增加服务消费消息,成本高。方案4通过订阅binlog更新redis,使用我们搭建的consumer服务作为mysql的slave,订阅binlog,解析出更新内容,再更新到redis。优点:在mysql压力小的情况下,延迟低;它与业务完全脱钩;它解决了时间问题。缺点:单独搭建同步服务,引入binlog同步机制,成本比较高。总结方案选择首先确认产品的时延要求。如果要求特别高,数据可能会变化,就不要使用缓存。一般来说,解决方案1就足够了。笔者咨询过4、5个团队,基本都是采用方案1,因为可以使用缓存方案,所以通常是读多写少的场景。同时,业务对延迟有一定的容忍度。.方案一没有开发成本,其实更实用。如果你想增加更新的即时性,选择选项2,但不需要做重试保证之类的。Option3和Option4针对时延要求高的业务,一种是push模式,一种是pull模式,Option4可靠性更强。既然大家都愿意花时间在处理消息的逻辑上,不如一步到位。使用选项4。结论通常,选项1就足够了。如果延迟要求高,直接选择选项4。如果是面试场景,从简单到复杂,面试官一步步提问,我们一点点推演,宾主尽兴。
