上一篇面试官:如何保证Redis缓存和数据库数据的一致性?直击灵魂的连环七问你扛得住么?文章中我们谈到保证Redis缓存和数据库数据一致性的四个面试题目,相信大家在遇到这些问题时已经能够轻松应对了。文章结尾处留下了面试官可能深入追问的几个问题,今天我们再来一起探讨下。
面试官:更新数据库成功、删除缓存失败了,怎么处理?
一名优秀的程序员在设计技术方案一定要考虑到各种异常Case,比如系统宕机、并发问题等等。软件编码开发中要分离控制和逻辑,控制指的是程序流转相关的,比如多线程、异步、服务发现等,逻辑则是实实在在的业务逻辑。要让业务逻辑正确运行,控制的代码往往占有更多的比重,这样才能让我们的程序更加健壮。
更新数据库成功之后,服务宕机了或者服务与缓存服务器断开网络连接了,都会导致删除缓存失败。如下图所示:

对于此问题,可参考如下三种解决方案:
方案一:简单重试三次+最终删除失败的报警,人工介入处理
如果更新不频繁,删除缓存失败的概率还是比较低的,删除失败后一般连续重试三次即可,最终删除失败记录日志,添加报警,人工介入处理即可。
优点:简单、实现成本低
缺点:一旦失败,需要人工介入,处理慢,有可能业务有损
方案二:删除缓存失败则放入消息队列,消费这些消息,重试删除,重试次数达一定阈值后,报警,人工介入处理
处理流程如下图所示[1]:

优点:自动处理,一直重试基本能保障最终删除成功
缺点:引入消息队列,复杂度相对较高
方案三:订阅数据库日志(如MySQL binlog),解析更新操作清除缓存
这里较为流行的是阿里巴巴开源的作为MySQLbinlog增量获取和解析的组件canal[2],canal 需要高可用,依赖 zookeeper 作为分布式协调组件来实现 HA ,维护成本相对较高。如下图所示[1]:

优点:通用方案,不侵入业务,各业务可共用这一套方案
缺点:复杂度高,维护成本高
面试官:“更新数据库、删除缓存”的方案适用于什么场景呢?适合秒杀场景使用么?
适合“读多写少”的业务场景,不适合秒杀场景,因为秒杀场景需要大量的更新库存,如果更新操作都需要先更新数据库,那数据库肯定无法承受如此大的流量。
面试官:那缓存应用还有哪些策略?分别适用于什么场景呢?
缓存策略通常包括三类,旁路缓存模式 Cache-Aside 、读穿透Read-Through/直写Write-Through模式、异步回写模式Write-Behind。
Cache-Aside 是应用最广泛的策略,上面的方案就是这种模式,这里不再赘述。
读穿透Read-Through模式,它的流程和Cache-Aside类似,不同点在于Read-Through中多了一个访问控制层,读请求只和该访问控制层进行交互,而背后缓存命中与否的逻辑则由访问控制层与数据源进行交互,业务层的实现会更加简洁,并且对于缓存层及持久化层交互的封装程度更高,更易于移植。如下图所示[1]:

直写模式Write-Through,它也增加了访问控制层来提供更高程度的封装。不同于 Cache-Aside 的是,Write-Through 直写模式在写请求更新数据库之后,并不会删除缓存,而是更新缓存。此种模式的读操作简单,更新操作配合分布式事务能保障数据的强一致。如下图所示[1]:

Write behind 意为异步回写模式,它也具有类似 Read-Through/Write-Through 的访问控制层,不同的是,Write behind 在处理写请求时,只更新缓存而不更新数据库,对于数据库的更新,则是通过批量异步更新的方式进行的,批量写入的时间点可以选在数据库负载较低的时间进行。如下图所示[1]:

在 Write-Behind 模式下,写请求延迟较低,减轻了数据库的压力,具有较好的吞吐性。但数据库和缓存的一致性较弱,同时,缓存的负载较大,如果缓存宕机会导致数据丢失,所以需要做好缓存的高可用。显然,Write behind 模式下适合大量写操作的场景,常用于电商秒杀场景中库存的扣减。
面试官:生产环境中MySQL等数据库一般都会使用主备模式,应用程序会写主库读从库。如果采用写数据库清缓存+读数据库更新缓存的方案,读请求可能因为从库没同步最新的数据而更新旧数据到缓存中,造成缓存不一致,这种情况如何解决?
笔者当时就是在这个问题上折戟的,这个问题一下把“更新数据库,之后删除缓存”的方案打死了啊。。。因为备库的存在,我们的写请求删除了缓存后,数据还没来得及同步到备库,此时读请求从备库获取数据,得到的是旧值,更新到缓存后,就会造成缓存与数据库的数据不一致。而且主备同步的延迟涉及网络传输,通常是几ms,所以请求量大时,这种不一致问题发生的概率还是比较大的,这是不能接受的。
如何解决呢?其实我们介绍删除缓存失败的问题时,介绍的方案三就可以解决这一问题。当我们消费到binlog后,删除缓存就可以了,这样就能保障数据库操作都完成后再删除缓存,来避免主从同步下删除缓存过早的问题,从而保障了缓存和数据库的最终一致性。
所以生产环境中如果数据库采用了主从同步、并且业务程序开启了写主读从,那么,采用“ Cache-Aside 结合消费数据库日志做补偿”的方案才是比较稳妥的。
结语
各个方案的适用场景,汇总到如下表格:

在实际面试过程中,尤其是二面,不少面试官会采用持续追问的方式去探候选人的“底”,如果对一个知识点,特别是自己项目中用到的技术,如果不能很好地回答应对这些问题,往往会给面试官留下技术掌握深度不够、工作中思考总结不足的印象。
针对Redis这个案例,我们日常做需求如果使用到一个技术,可以先看看业界方案是怎么做的,搞清楚这些方案的优缺点,也看下是否契合我现在的落地场景,总结输出到自己的技术方案中,或者每个技术方案中都设计一两个备选方案拓宽思路,结合线上真实环境去推演方案的合理性,设计预案,就能慢慢积累技术深度。
功夫在平时!这样,找工作时,我们应对面试官的连续追问也就更能够游刃有余了。
参考:
[1]https://cloud.tencent.com/developer/article/1932934
[2]https://github.com/alibaba/canal/
[3]https://zhuanlan.zhihu.com/p/479771075
[4]https://www.jianshu.com/p/37b2f60add41
[5]https://xiaolincoding.com/redis/architecture/mysql_redis_consistency.html
[6]https://time.geekbang.org/column/article/295812
笔者刚创建了一个面试互助&技术交流群,微服务框架开源大牛助阵,有问必答,欢迎大家扫码加入,一起学习交流!

本篇文章来源于微信公众号: 程序员Aike
微信扫描下方的二维码阅读本文

Comments NOTHING