背景
缓存是软件开发中非常有用的概念,数据库缓存是项目中不可避免的场景。而且缓存一致性的保证在采访中被反复问到。这里总结,并根据不同的要求,选择合适的一致性方案。
缓存是什么?
存储速度有区别。缓存是将低速存储的结果临时存储在高速存储中的技术。
如图所示,金字塔上方的存储可以作为下方存储的缓存。这个讨论集中在数据库缓存场景,并以redis为mysql 的缓存案例。
为什么需要缓存?
比如mysql通常支持完整的ACID特性。因为可靠性、持久性等因素,性能普遍不高,高并发查询会给mysql带来压力,导致数据库系统不稳定。同时也容易出现延迟。根据局部性原则,80%的请求将落在20%的热数据上。在多读少写的场景下,增加一层缓存对提高系统的吞吐量和健壮性非常有帮助。
存在问题
存储的数据可能会随时间变化,缓存中的数据会不一致。具体可容忍的不一致时间需要具体业务分析,但通常业务需要最终一致。
Redis作为mysql缓存
在通常的开发模式中,mysql作为存储,redis作为缓存,用来加速和保护mysql。但是,mysql数据更新时,redis如何保持同步?
强一致性同步的成本太高。如果追求强一致性,那么就不需要用缓存,用mysql就可以了。通常,考虑的是最终的一致性。
解决办法
方案一
通过key的过期时间,mysql更新时redis不更新。这种方法实现简单,但是不一致的时间会比较长。如果读请求非常频繁,过期时间很长,就会产生大量长期脏数据。
优势:
开发成本低,易于实现;
管理成本低,出问题的概率也会小。
不足:
完全看到期时间。时间过短容易导致缓存频繁失效,时间过长容易导致更新延迟过长。
选项2
在第一种方案的基础上,通过key的到期时间来延长,mysql更新时,redis也更新。
优势:
与第一种方案相比,更新延迟更小。
不足:
如果mysql更新成功,但redis更新不成功,则退化到第一种方案;
在高并发场景中,业务服务器需要同时连接mysql和redis。这是连接资源的双重损失,容易造成连接过多的问题。
方案3
第二种方案优化了redis的同步写入,增加了消息队列,redis更新操作交给kafka,可靠性由消息队列保证,然后建立一个消费者服务来异步更新redis。
优势:
消息队列可以使用一个句柄,很多消息队列客户端也支持本地缓存发送,有效解决了方案二中连接过多的问题;
利用消息队列实现了逻辑解耦。
队列本身是可靠的,redis通过人工提交的方式至少可以消费一次。
不足:
还可以解决不了时间问题。如果多个业务服务器分别处理对同一行数据的两个请求,举个栗子,a=1;a=5;如果在mysql中先执行第一个,在kafka中先执行第二个,数据会不一致。
引入了消息队列,同时增加了服务消费消息,成本高。
方案4
订阅binlog来更新redis,使用我们的消费者服务作为mysql的奴隶。订阅binlog,解析出更新的内容,然后更新到redis。
优势:
在mysql压力低的情况下,延迟低;
服务完全解耦;
时机问题解决了。
缺点:
单独构建同步服务并引入binlog同步机制的成本很高。
摘要
方案选择
首先,确认产品上的延时要求。如果要求非常高,数据可能会改变,不要不要使用缓存。
总的来说,方案一就够了。我咨询过4、5个团队,基本都是用方案一,因为缓存方案可以用,而且一般是多读少写的场景,业务对延迟有些包容。该方案没有开发成本,但实际上很实用。
如果你想增加更新的即时性,选择选项2,但是不需要做重试保证等等。
方案3,方案4是针对高延迟要求的业务,一个是推模式,一个是拉模式,方案4可靠性更强。既然大家都愿意在消息处理的逻辑上下功夫,不如一步到位用方案4。
结论
一般来说,方案1就足够了。如果延迟要求高,直接选择方案4。如果它一个面试场景,从简单到复杂,面试官会一步步提问,而我们I’稍微演绎一下,宾主皆大欢喜。
标签:Mysql方案场景