老话新谈之缓存一致性

news/2024/7/7 18:57:32

前言

缓存一致性常见的更新策略也比较多,如先更新数据库再更新缓存,先删缓存再更新数据库等等,我在理解的时候有些混乱,所以这个文章提供了一些理解上的技巧去理解缓存一致性。

为什么会有缓存一致性的问题

  1. 缓存与数据库是两套中间件,存在网络抖动之类的原因导致没有更新任一方的可能
  2. 数据库大多都是事务型的中间件,支持错误回滚,缓存大多是非事务型的中间件,这里缓存更新失败了没办法回滚

所以根因是缓存大部分不支持事务无法回滚。

怎么尽量解决缓存一致性的问题

操作二者必定有先后顺序,存在以下两个情况:

  1. 先操作缓存,再操作数据库。操作缓存成功,数据库更新失败,缓存无法回滚,数据不一致
  2. 先操作数据库,再操作缓存。操作数据库成功,缓存操作失败,可触发异常回滚数据库,数据一致

根据上述所列,只能先操作数据库,再操作缓存了。

操作缓存也分两种:

  1. 更新缓存数据,可能并发请求,后一请求更新缓存的数据被前一请求的更新覆盖了,导致数据不一致
  2. 删除缓存数据,并发请求,二者都使缓存失效,查询请求将数据库数据加载到缓存中,数据一致

根据上述所列,只能使缓存失效,查询请求加载数据到缓存中了。

所以,如果在不加任何重试措施的情况下,先操作数据库,再删除缓存是一个容错较好的方法。

缓存一致性的分类 & 存在的问题

Client 维护缓存 & 数据库的一致性

  1. 更新缓存 -> 更新数据库

    图片1

@startuml
Database Database   as DB
entity   Cache      as Cache

transaction1 -> Cache: update data
transaction1 <-- Cache: update result

transaction1 -> DB: update data
transaction1 <-- DB: update result

@enduml
  • 可能出现的数据不一致

​ 数据不一致:更新缓存成功了,更新数据库失败了,有数据不一致的问题,直到缓存超时失效或又一更新请求操作成功都会不一致

  • 改进方式

    若保证更新数据仅有少数的服务更新,可以将更新数据库请求入队处理,且可加入重试机制。但是队列的加入会增大系统复杂度,并且重试以及缓存更新顺序不一致会加剧数据不一致

  1. 更新数据库 -> 更新缓存

@startuml
Database Database   as DB
entity   Cache      as Cache

transaction1 -> DB: update data
transaction1 <-- DB: update result

transaction2 -> DB: update data
transaction2 <-- DB: update result


transaction2 -> Cache: update data
transaction2 <-- Cache: update result

transaction1 -> Cache: update data
transaction1 <-- Cache: update result

@enduml
  • 可能出现的数据不一致

​ 数据不一致:如 t1 先更新数据库,t2 在 t1 更新缓存前把数据库缓存都更新完了,t1 再更新缓存,这时候缓存上是 t1 的数据,数据库是 t2 的数据

  • 改进方式

    若保证更新数据仅有少数的服务更新,可以将更新数据库请求入队处理,但是队列更新的引入增大了系统复杂度

  1. 删除缓存 -> 更新数据库

@startuml
Database Database   as DB
entity   Cache      as Cache

transaction1 -> Cache: delete data

query1 -> DB: select data
query1 -> Cache: insert data

transaction1 -> DB: update result

@enduml
  • 可能出现的数据不一致

    1. 如图所示,更新请求先删除缓存,查询请求从缓存获取不到数据从数据库获取数据(老数据)加载到缓存中,更新请求更新数据库
    2. 这样的流程会导致查询请求加载老数据到缓存中,后续更新请求更新新数据到数据库中,导致数据不一致
  • 改进方式

    暂无。

  1. 更新数据库 -> 删除缓存

@startuml
Database Database   as DB
entity   Cache      as Cache

query1 -> DB: select data
transaction1 -> DB: update result
transaction1 -> Cache: delete data
query1 -> Cache: insert data

@enduml
  • 可能出现的数据不一致

    查询请求先拿到数据,在插入缓存前更新请求进来更新数据库并使缓存失效,这个请求比较罕见

    1. 发生的场景
      1. 查询请求所在机器请求缓存比更新请求做完的整个流程都要慢
    2. 发生的概率
      1. 很低。因为操作缓存一般会比操作数据库要快
  • 改进方式

    1. 变更数据记录变更事件
      1. 步骤
        1. 更新数据同步记录一个事件在本地内存中
        2. 查询请求在插入缓存前查询事件,如果存在变更则查数据库获取最新数据
        3. 如果此数据在查询请求插入缓存过程中一直变更,这里需要先返回当前数据库结果给上游,再开异步任务轮训事件/数据库插入缓存
      2. 适用场景
        1. 只适用单节点

Server 维护缓存 & 数据库的一致性

  1. Read though/Write though

    • read though

    @startuml
    Database Database   as DB
    entity   Cache      as Cache
    
    query -> repository: select data
    
    repository -> cache: get data
    repository -> DB: get data
    DB -> repository: return data
    repository -> cache: update data
    repository -> query: return data
    
    @enduml
    
    • wirte though

    @startuml
    Database Database   as DB
    entity   Cache      as Cache
    
    transcation -> repository: update data
    
    repository -> cache: update data
    repository -> DB: update data
    DB -> repository: return result
    repository -> transcation: return result
    
    @enduml
    
  • 可能出现的数据不一致
    • 程序没有优雅关闭,更新请求先更新了缓存,但还没更新数据库,数据丢失
    • 更新缓存成功,更新数据库失败导致的数据不一致
  • 适用场景
    • 更新数据库极低概率失败
    • 程序有优雅关闭功能
  • 改进方式
    • 暂无
  1. Write Behind

@startuml
Database Database   as DB
entity   Cache      as Cache

query -> repository: query data

repository -> cache: query data
repository -> DB: query data
DB -> repository: return data
repository -> cache: update data

repository -> query: return data

@enduml

@startuml
Database Database   as DB
entity   Cache      as Cache

transcation -> repository: update data

repository -> cache: update data

repository -> DB: batch update data

@enduml
  • 可能出现的数据不一致
    • 程序没有优雅关闭,更新请求先更新了缓存,但还没更新数据库,数据丢失
    • 批量更新数据库失败导致的数据不一致
  • 适用场景
    • 更新数据库极低概率失败
    • 程序有优雅关闭功能
  • 改进方式
    • 暂无

参考

https://coolshell.cn/articles/17416.html

本文首发于cartoon的博客

转载请注明出处:https://cartoonyu.github.io


http://lihuaxi.xjx100.cn/news/1230341.html

相关文章

UniApp组件封装

什么是UniApp组件&#xff1f; UniApp是一个跨平台的开发框架&#xff0c;允许开发者使用Vue.js编写一次代码&#xff0c;然后将其发布到多个平台&#xff0c;包括iOS、Android和Web。在UniApp中&#xff0c;组件是构建用户界面的基本单元&#xff0c;它们可以重复使用&#x…

Vcpkg介绍及使用

Vcpkg用于在Windows、Linux、Mac上管理C和C库&#xff0c;极大简化了第三方库的安装&#xff0c;它由微软开源&#xff0c;源码地址&#xff1a;https://github.com/Microsoft/vcpkg&#xff0c;最新发布版本为2023.04.15 Release&#xff0c;它的license为MIT。 在windows上安…

Docker部署开源项目Django-CMS企业内容管理系统

Docker部署开源项目Django-CMS企业内容管理系统 一、Django-CMS介绍1.1 Django-CMS简介1.2 Django-CMS特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、下载django-cms-quic…

YOLOv5使用自定义数据集实验

上一篇博文中介绍了YOLOv7训练自定义数据集&#xff0c;在这篇文章中&#xff0c;我们主要记录YOLOv5模型的实验过程&#xff0c;用于对比实验。 YOLOv5与YOLOv7毕竟一母同胞&#xff0c;因此部署起来也是极为类似。 数据集 数据集使用的与YOLOv7的实验数据集一样&#xff0c;…

使用 Pycharm 调试远程代码

文章目录 背景同步远程代码Interpreter注意点 背景 工作机是一台 Windows 电脑&#xff0c;而很多时候需要在 Mac 电脑上编码、配合 iPhone 模拟器&#xff0c;所以我以前是用 VNC 或者向日葵来远程 Mac 来编程&#xff0c;其实还能接受&#xff0c;但是最让我不舒服的是快捷键…

万字详解普遍操作系统进程七态与Linux进程七态

作为一个称职的系统管理员&#xff0c;为了更熟悉进程的管理流程&#xff0c;我们必须要知道进程的不同状态所对应的意义。 目录 了解进程状态普遍操作系统的概念就绪状态运行状态等待状态阻塞状态挂起状态暂停状态终止状态 Linux下的进程状态R (running)运行状态S (sleeping)可…

numpy中的np.random.rand、np.random.randn、np.random.randint、np.random.uniform等用法

随机数生成方法 1、np.random.rand(d0, d1, …, dn) np.random.rand(d0, d1, …, dn)&#xff1a;生成一个指定形状的[0, 1)之间均匀分布的随机数数组。参数d0, d1, …, dn指定了生成的随机数数组的维度。 import numpy as nprandom_array np.random.rand(3, 2) # 生成一个…

点云综述(整理自网络资源)

目录 一、什么是点云 二、如何获取点云 1、三维激光扫描仪 2、双目相机 双目测距基本原理 视差图 双目测距的优点与难点 3、RGB-D相机 RGB-D什么意思 RGB-D相机的分类 RGBD相机的缺点&#xff1a; RGBD相机的优点 三、点云有哪些研究方向 1、基于点云的分类 2、基于…