当前位置: 首页 > news >正文

缓存数据库一致性

文章目录

  • 常用的一致性策略
    • Cache Aside旁路缓存
    • read through或write through
    • write back
    • 写请求先写缓存还是先写数据库,写缓存还是删除缓存,怎么操作才能保证缓存和数据库的一致性?
      • 先更新缓存,再更新数据库
      • 先更新数据库,再更新缓存
      • 先删除缓存,再更新数据库
      • 延迟双删
      • 先更新数据库,再删除缓存
  • 说明

常用的一致性策略

Cache Aside旁路缓存

  • 定义:读请求先从缓存中去读,读不到再从数据库中读,然后写入缓存
  • 特点:业务代码既要操作缓存,又要操作数据库,以数据库的数据为主,缓存只是暂时存储数据而已。

read through或write through

  • 设计思想:不直接操作数据库,只操作缓存,然后让缓存自身去操作数据库。
  • 定义read through指每次读都要读缓存,如果有就返回数据,没有缓存自身从数据库中读数据,然后写入缓存;write through指写数据的时候直接写缓存,缓存自身去更新数据库。也就是说我们只需操作缓存即可,剩余操作由缓存中间件自身去完成。

write back

  • 定义:只操作缓存,然后让缓存自身去操作数据库。与上面不同之处是写回是批量的、异步的去写数据库。
  • 适合写多读少的场景。但是由于是异步写入数据库,所以有数据丢失的风险。

写请求先写缓存还是先写数据库,写缓存还是删除缓存,怎么操作才能保证缓存和数据库的一致性?

先更新缓存,再更新数据库

可以看到此时缓存值为B,数据库值为A,出现缓存和数据库不一致。因为更新缓存和数据库的操作不是原子性的,所以会出现并发问题。

先更新数据库,再更新缓存

可以看到此时缓存值为A,数据库值为B,也出现缓存和数据库不一致。

先删除缓存,再更新数据库

这个方法也会出现缓存和数据库不一致的情况

延迟双删

先删除缓存再更新数据库,让线程等待一会儿然后再删数据库。为什么要等一会再删,这是要让另外一个线程能在等一会的这个时间去完成读数据库并写缓存这个操作,然后给它删了。

那么问题来了,要延迟多久(等多久)?

这要考虑当时的网络情况怎么样,服务器的负载和数据库的负载等情况,没有一个标准的时间。

先更新数据库,再删除缓存

这个方法也会出现不一致的情况,但是概率非常小,因为必须满足两个条件:

  1. 刚好是这个 key 刚好过期,然后刚好又要更新数据
  2. 请求2刚更新完数据,然后删除缓存之后,请求1才写入缓存。

也就是说写缓存的速度要慢于更新数据库+删除缓存的速度,这个概率是非常低的。所以先更新数据库再删除缓存在绝大多数情况下都是可行的。


那么问题来了,如果更新数据库成功,删除缓存失败了怎么解决呢?

  • 把删除缓存的操作放到 MQ 中,MQ 去异步地删除缓存。如果删除失败了,那就重试。
  • 监听数据库的bin log,更新数据库成功会产生一条bin log,因此就可以监听这个bin log,然后删除缓存。同样地,如果删除失败了,那就重试。这个方法的优点是不入侵业务代码,比使用 MQ 的方案好。

但是实际上公司的 MySQL 都是主从部署读写分离的,主库只能写从库只能读,然后主库更新的数据需要花一点时间同步给从库。所以当请求1先更新了这个数据库,然后异步地删除了缓存,但是写的是主库,从库还没写,从库需要等待主库同步数据过来,那从库还没等到主库传来的数据此时又来了一个请求读了从库的旧数据然后写入了缓存,缓存和数据库又出现不一致了,但是这种概率也是非常低的,如果非要解决这个问题,那可以在主库更新之后的一段时间之内,让其他请求强制去读主

综上所述,要想保证缓存和数据库的完全一致性,几乎是完全不可能的,现有的手段只能尽可能地去保证缓存和数据库在绝大多数的情况下是一致的,然后给数据设置一个过期时间。即便在非常极端的情况下不一致了,也能让这个脏数据过期失效。

说明

本文参考@程序员回家养猪博主的视频进行总结和编写。

http://www.jsqmd.com/news/653551/

相关文章:

  • OpenClaw 完整安装教程与最新版安装包
  • iOS App审核通关指南:MFi配件集成与PPID填写实战
  • PyTorch 2.8开源镜像实战教程:在RTX 4090D上部署本地化AI客服视频应答系统
  • 终极指南:5步实现老Mac升级最新macOS的完整方案
  • 2026年进口gl8改装用户口碑推荐厂家 - 品牌宣传支持者
  • GOOSE协议深度解析:从报文帧结构到变电站实时通信实战
  • 告别Windows依赖:在Ubuntu 22.04下用命令行搞定RK3588系统烧录与分区定制
  • ClaudeCode安装与使用
  • 树莓派4B+nrf52840 dongle搭建Thread边界路由保姆级教程(含常见错误排查)
  • OSNet轻量化设计剖析:从基础卷积到OSBlock的演进之路
  • OneMore插件终极指南:3分钟掌握表格全选技巧,OneNote效率飙升300%
  • Keil MDK AC6迁移后printf不打印?手把手教你修复串口重定向(附ST官方方案)
  • IEEE 802.3u是1995年发布的快速以太网标准,将以太网传输速率从10Mbps提升至100Mbps
  • 3步掌握:终极免费文档下载神器使用全攻略
  • ChatGLM3-6B-128K代码实例:Function Call调用示例
  • 【缺陷检测】k-means分割Otsu阈值检测水果和蔬菜缺陷(外部和内部缺陷)【含Matlab源码 15334期】
  • 三点定圆心半径易语言实现
  • 告别链接错误:手把手教你用gcc在Linux下正确编译和调用静态库.a文件
  • Windows 下 OpenClaw 快速搭建与使用指南
  • SCI投稿全周期沟通指南:从投稿信到校稿信的实战模板与策略
  • 基于STM32LXXX的模数转换芯片ADC(HX710A)驱动C程序设计
  • 软件离线分析中的查询性能优化
  • Hot100部分
  • 从零到英雄:CodeCombat游戏化编程学习之旅
  • 59、实现:页头在顶部,页脚永远在页面底部
  • 现代智能汽车中的无线技术11.7——TCU之远程OTA升级业务
  • Excel也能玩转熵权法?手把手教你不用编程做指标权重分析
  • PowerDMIS迭代法
  • Google Colab | GPU连接失败背后的资源博弈与应对策略
  • Unity URP 下的流体模拟 深入解析 Navier-Stokes 方程与浅水方程的数学原理