缓存高可用设计
最终一致性分布式缓存场景:
一、异地机房互备
-
查询本地缓存前降级:若本地redis集群出现故障,可以在配置平台人工快速切换到查询另一侧的服务。
-
查询本地缓存后降级:本地处理结束,若出现特定错误码("OPERATE_REDIS_ERROR")则可降级到查询另一侧服务。该功能也需要手工配置开关来启用。
二、QMQ和Kafka湖北
- 缓存更新流程通过MQ来驱动,虽然公司的MQ中间件服务由专人维护,但是万一出现问题长时间不能恢复,对我们来说将是致命的。所以我们决定同时采用Kafka和QMQ两种中间件来作为互备方案。默认情况下对于全表扫描任务和binlog消费这类大批量消息场景使用Kafka来驱动,而其他场景通过QMQ来驱动。所有的场景都可以通过开关来控制走Kafka或者QMQ。目前该功能可通过配置管理平台来实现快速切换。
- 在极端情况下,可能出现Redis数据丢失的情况,如主机房(A机房)突然断网,redis集群切换过程出现数据丢失或同步错乱,此时很可能无法通过自动触发来补齐数据,因此设计了全表快速扫描的补偿机制,通过多任务并行调度,可在30分钟内将全量数据完成刷新。此功能需要人工判断并触发。
强一致性
可选方案:
1、分库分表:成本和复杂度相对较高,只是查询流量高
2、读写分离:处于数据库性能的考虑,MYSQL大部分采用的异步复制,保证不了强实时性。
(1):缓存读取和DB更新并发
完整缓存删除策略
方法:在更新操作中,在锁的范围内,先更新DB,再删除缓存。
锁类型:选择redis同介质的redis分布式锁,因为redis服务不可用导致锁的处理失败,对于缓存本身不可用key自动走降级方案。
锁粒度:
方案一:事务提交后加锁,只锁定缓存操作,对原事务无任何影响,但是在事务提交后删除缓存之间存在与查询的并发可能性。
方案二:事务在提交前加锁,删除缓存后解锁。在满足一致性要求的前提性,锁的粒度做到最小,但是增加了事务的范围,若redis出现超时或者事务时间拉长,会影响DB性能。
方案三:在事务开始之前加锁,删除缓存后解锁。,锁的范围越大,但是能满足一致性要求,对单个DB事务也基本无影响。对同一个用户来说,数据更新不频繁,锁范围大点可以接受。
方案三在redis出现故障后,会自动降级到方案一。
二、删除缓存失败的补偿
要考虑的问题:如果更新DB成功后但是删除缓存失败后如何处理? 情况(应用服务器故障、网络故障、redis故障)
若应用服务器突然故障,则服务整体不可用,跟缓存关系不大了。若是由于网络、redis故障灯原因导致删除缓存失败,此时查询缓存也不可用,查询走DB,但是需要
记录那些数据做了变更,待redis可用后进行恢复,需要将中间变更的记录全部删除。
构建一张简易的记录表(代表发生变更的DB数据),每次DB变更后,将该变更记录表的插入和业务DB操作方在一个事务中处理。
事务提交后,对应的变更记录持久化,之后删除缓存,若缓存删除成功,则将对应的记录表数据也删除掉。
若删除失败,则可根据记录表的数据进行补偿删除,而在redis的恢复流程中,需要校验记录表中是否存在数据,若存在则证明有变更,缓存数据未清除。
删除操作还要异步重试,来避免偶尔超时引起的缓存删除失败。
缓存熔断和恢复 redis集群不可用(网络问题、redis集群问题)
缓存不应该是强依赖的,仅仅做辅助,缓存不可用,主业务也要保持可用。
(1)缓存熔断
我们的熔断判断逻辑为:每个redis操作都try-catch异常,并做计数统计(不区分读写操作),若在M秒内出现N次异常则开启熔断。我们的场景下设置为10秒内出现50次异常就熔断,可根据自身场景设置,需要注意的是如果redis请求次数比较少,则需要在配置上保证在M秒内至少出现N次请求。
此外熔断开关的配置是放在应用服务器的内存中,即单机熔断,而非集群熔断,这样做的原因是,redis服务不可用有可能是单机与redis服务的连通性问题导致,而在其他机器上依然可以访问缓存。