缓存一致性设计方案

本文探讨了缓存一致性问题,分析了先淘汰缓存再更新数据以及先更新数据再淘汰缓存两种策略的潜在问题。提出了实用的延时二次淘汰方案,以减少数据库与缓存数据不一致的风险。在更新数据后,通过生成延时任务来淘汰缓存,确保在缓存更新过程中避免不一致状态。
摘要由CSDN通过智能技术生成

前言

公司项目中发生了缓存一致性的问题,具体场景如下:

  1. 系统的业务配置更新为生效状态后,业务服务实际使用的配置数据还是旧版本的数据
  2. 偶现业务服务使用的配置数据不完整,造成业务流程异常

项目中引入了缓存,排查问题时发现 redis 中缓存的数据就是旧版本数据,而且存在缓存中配置数据不完整的现象。分析代码,发现导致缓存数据与数据库数据不一致的原因有两个,本文主要分析解决 缓存淘汰设计不合理 的问题

  1. MySQL读写分离
    项目中使用了公司的读写分离框架,查询数据时默认读从库。因为主从延迟 的存在,主库数据更新后需要一定时间才能在从库读到更新后的数据,如果在这期间缓存已经失效并且恰好有查询进来,只能查询到从库旧数据,并且会将其缓存到 redis。这个点比较好解决,只要指定查询主库就可以
  2. 缓存淘汰设计不合理
    项目代码中将淘汰缓存的操作包裹在 @Transcational 注解的方法的末尾,这样做实际是在事务提交前就淘汰了缓存,当并发较高时,如果查询在更新事务执行时进行,并且持续到更新事务结束,这样未加入同一个事务的多表查询就可能存在有些表数据能查到,有些表数据在更新事务结束后查不到的情况,也就造成了缓存中配置数据的不完整

1. 缓存一致性

应用系统通常会引入缓存来缓解数据库压力,这样可以提高系统吞吐,提升用户体验,但也带来了数据污染的风险,也就是缓存一致性问题。所谓缓存一致性指的是缓存数据和数据库数据之间的一致,如果没有合理的缓存设计,很容易导致缓存与数据库之间的不一致问题

2. 缓存一致性设计分析

缓存穿透等概念 中笔者大致介绍了缓存相关概念,缓存一致性问题总是由数据库源数据更新引发,所以在数据更新时首先要确定缓存的处理方式,通常的处理策略是淘汰缓存而不是更新缓存,原因如下:

  1. 更新数据库时如果同步更新缓存数据,由于二者一般属于不同的中间件,很难保证原子操作,则必然存在并发更新缓存造成的数据不一致
  2. 更新缓存数据通常需要序列化,如果将其和数据库更新的操作绑定在一起,会额外提高数据更新操作的性能成本

基于以上共识,现在要考虑的就是在更新数据时如何淘汰缓存,直观的处理方式有两种,但是二者都有一定的问题,下文将详细说明

  1. 先淘汰缓存,再更新数据
  2. 先更新数据,再淘汰缓存

2.1 先淘汰缓存,再更新数据

这个方案可能导致问题的处理流程如下图所示,示意图中有两个线程在操作缓存,具体步骤如下:

  1. 线程1 进行数据库更新操作,在数据库更新之前首先淘汰掉缓存
  2. 线程1 开启更新事务,执行数据库更新
  3. 线程1 执行更新事务时,线程2 执行数据查询,首先去查询缓存
  4. 由于 线程1 已经淘汰掉缓存,此时线程2 未查询到缓存,只能去查询数据库
  5. 线程1更新事务执行耗时比较长线程2 查询数据库只能查到旧的数据,并在查到旧数据后将其缓存起来
  6. 线程1 更新事务执行完毕,更新后的最新数据对外可见,但是缓存中已经被线程2保存了旧数据
  7. 由于以上原因,数据库数据与缓存数据出现不一致

在这里插入图片描述

2.2 先更新数据,再淘汰缓存

这个方案的出现异常的流程如下:

  1. 线程1 进行数据库更新操作,开启更新事务
  2. 线程2 执行数据查询,此时缓存中数据不存在或者已经过期淘汰,只能去查数据库
  3. 线程2 查询数据库,此时只能查到旧数据,但是由于网络波动、GC 等原因,一直没有拿到数据库返回的数据
  4. 线程2 等待数据库返回数据期间,线程1 更新事务执行提交,数据库最新数据对外可见
  5. 线程1 更新事务执行完毕,淘汰掉旧的缓存数据
  6. 线程2线程1 淘汰缓存之后终于拿到了数据库返回的旧数据,并使用旧数据去更新缓存
  7. 由于以上原因,数据库数据与缓存数据出现不一致

在这里插入图片描述

3. 实用方案-延时二次淘汰

针对上一节提到的问题,一个能较大限度降低缓存一致性风险的方案是进行缓存二次淘汰,其处理流程如下:

  1. 线程1 进行数据库更新操作,在数据库更新之前首先淘汰掉缓存
  2. 线程1 开启更新事务,执行数据库更新
  3. 线程1 执行更新事务时,线程2 执行数据查询,首先去查询缓存
  4. 由于 线程1 已经淘汰掉缓存,此时线程2 未查询到缓存,只能去查询数据库,此时查到旧数据,但是由于网络波动、GC 等原因,一直没有拿到数据库返回的数据
  5. 线程1 的更新事务终于提交,数据库最新数据对外可见
  6. 线程1 更新事务执行完毕,生成延时任务用于淘汰缓存
  7. 线程2 此时终于拿到了数据库返回的旧数据,并使用旧数据去更新缓存
  8. 由于以上原因,数据库数据与缓存数据出现短时不一致
  9. 线程1提交的延时任务执行,淘汰掉 线程2 更新的缓存数据,缓存中没有数据,下次查询必然要走数据库,则消除了缓存不一致的问题

需注意,延时任务的延时时间需要大于读取缓存的平均耗时(也即是线程2读取缓存,更新缓存这个过程的耗时)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值