首页 > 数据库 >数据库和缓存如何保证一致性

数据库和缓存如何保证一致性

时间:2024-04-15 17:25:38浏览次数:11  
标签:缓存 请求 删除 数据库 更新 一致性 数据

保证数据库和缓存之间的一致性是在许多应用程序中面临的挑战。数据库和缓存是两个不同的存储层,具有不同的特性和行为。在使用缓存的同时,确保数据库和缓存之间的数据一致性是至关重要的。

 

针对读请求,流程较简单,先读取缓存,缓存命中则返回结果,缓存未命中则读取数据库,并将读取的数据缓存到缓存中。

 

而针对写请求,就比较复杂了,有多种情况需要考虑。

 

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

A 请求先将数据库的数据更新为 1,然后在更新缓存前,请求 B 将数据库的数据更新为 2,紧接着也把缓存更新为 2,然后 A 请求更新缓存为 1

此时,数据库中的数据是 2,而缓存中的数据却是 1,出现了缓存和数据库中的数据不一致的现象。

 

如果业务对缓存命中率有很高的要求,可以采用这种方案,因为更新缓存并不会出现缓存未命中的情况。

优化数据不一致的方法:

  • 在更新缓存前先加个分布式锁,保证同一时间只运行一个请求更新缓存,就会不会产生并发问题了,当然引入了锁后,对于写入的性能就会带来影响。
  • 在更新完缓存时,给缓存加上较短的过期时间,这样即时出现缓存不一致的情况,缓存的数据也会很快过期,对业务还是能接受的。

 

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

A 请求先将缓存的数据更新为 1,然后在更新数据库前,B 请求来了, 将缓存的数据更新为 2,紧接着把数据库更新为 2,然后 A 请求将数据库的数据更新为 1

此时,数据库中的数据是 1,而缓存中的数据却是 2,依然出现了缓存和数据库中的数据不一致的现象。

 

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

A请求要更新数据,先删除缓存,在更新数据库的过程中,B请求读取该数据,因缓存未命中,读取了数据库中未更新的数据1,并将结果写入了缓存

此时,数据库中的数据是 2,而缓存中的数据却是 1,所以,在读写并发时,缓存和数据库中的数据还是有可能不一致。

 

优化方法:延时双删

先执行缓存清除操作,再执行数据库更新操作,延迟 N 秒之后再执行一次缓存清除操作,这样就不用担心缓存中的数据和数据库中的数据不一致了。

不过,这个N秒是多少需要结合业务情况来判断;另外,如果删除缓存后,在更新数据库时失败了,会导致缓存未命中而访问数据库,但数据库未更新,最终会访问到旧数据。

 

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

从上面的理论上分析,先更新数据库,再删除缓存也是会出现数据不一致性的问题,但是在实际中,这个问题出现的概率并不高。

因为缓存的写入通常要远远快于数据库的写入,所以在实际中很难出现请求 B 已经更新了数据库并且删除了缓存,请求 A 才更新完缓存的情况。

而一旦请求 A 早于请求 B 删除缓存之前更新了缓存,那么接下来的请求就会因为缓存不命中而从数据库中重新读取数据,所以不会出现这种不一致的情况。

不过,以上都是基于更新数据库与删除缓存都成功的情况下,实际中,删除缓存操作可能失败,需要别的方法进行优化。

 

如上先更新数据库,再删除缓存的策略就是目前广泛应用的Cache-Aside 策略,也称为旁路缓存模式。

针对Cache-Aside 策略并发高时,读取数据不一致的问题,可通过加锁解决:在写请求中保证“更新数据库&删除缓存”的串行执行为原子性操作(同理也可对读请求中缓存的更新加锁),不过会带来性能损耗。

对与删除缓存可能失败的问题,则通过重试机制或订阅 MySQL binlog的方式进行优化。

 

重试机制:

通过引入消息队列,将第二个操作(删除缓存)要操作的数据加入到消息队列,由消费者来操作数据。

  • 如果应用删除缓存失败,可以从消息队列中重新读取数据,然后再次删除缓存;当重试超过的一定次数,还是没有成功,则向业务层发送报错信息。
  • 如果删除缓存成功,就要把数据从消息队列中移除,避免重复操作,否则就继续重试。

 

订阅 MySQL binlog,再操作缓存:

更新数据库成功时,mysql会产生一条变更日志,记录在 binlog 里,可以通过订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除,如阿里巴巴开源的Canal 中间件。

Canal 模拟 MySQL 主从复制的交互协议,把自己伪装成一个 MySQL 的从节点,向 MySQL 主节点发送 dump 请求,MySQL 收到请求后,就会开始推送 Binlog 给 Canal,Canal 解析 Binlog 字节流之后,转换为便于读取的结构化数据,供下游程序订阅使用。

 

5.为什么是删除缓存,而不是更新缓存?

在保证缓存与数据库一致性时,通常会选择删除缓存而不是更新缓存的原因有以下几点:

  1. 数据一致性保证:通过删除缓存,可以确保下一次读取时从数据库中获取最新的数据。如果只是更新缓存,存在并发读取的情况下,可能会导致读取到旧的或不一致的数据。通过删除缓存,可以强制应用程序在下一次读取时重新从数据库中获取最新的数据,从而保证了数据的一致性。
  2. 简化实现和处理复杂性:更新缓存可能引入更多的复杂性和实现难度。例如,如果在更新缓存时发生错误,可能会导致缓存和数据库之间的数据不一致。而删除缓存的操作相对简单,只需将缓存中的数据删除,下一次读取时再重新加载最新的数据。
  3. 避免缓存中脏数据的存在:在某些情况下,数据库中的数据可能因为错误或异常情况而被修改或删除,导致缓存中的数据变为脏数据。通过删除缓存,可以避免脏数据的存在,确保下一次读取时从数据库中获取最新的、正确的数据。

 

参考:https://xiaolincoding.com/redis/architecture/mysql_redis_consistency.html

 

 

 

 

 

 

  • 删除重试机制

标签:缓存,请求,删除,数据库,更新,一致性,数据
From: https://www.cnblogs.com/Xinenhui/p/18131635

相关文章

  • openGauss AI4DB-数据库自治运维
    AI4DB:数据库自治运维如上文所述,AI4DB主要用于对数据库进行自治运维和管理,从而帮助数据库运维人员减少运维工作量。在实现上,DBMind的AI4DB框架具有监控和服务化的性质,同时也提供即时AI工具包,提供开箱即用的AI运维功能(如索引推荐)。AI4DB的监控平台以开源的Prometheus为主,DBMind提......
  • 数据库SQL注入攻击以及解决方案
    数据库SQL注入攻击以及解决方案--数据库SQL注入攻击selectcount(*)from表whereLoginID='lqwvje'andpwd='123'--拿上面的一个经常用的用户登入实例正常情况下是没有问题count>0即可以登入成功--用户名一但输入了数据库特殊字符如一下一段代码那就可以正常登入......
  • Sql Server数据库 读写分离之发布与订阅
    SqlServer数据库读写分离之发布与订阅1、配置分发2、发布3、订阅订阅我在虚拟机上,注意配置分发,发布,订阅我在这里,三个是同一内网,内网一定要联通,服务器的端口也要打开,不然没发访问......
  • Python对Sql Server数据库增删改查
    Python对SqlServer数据库增删改查#如果电脑上没有安装mssql模块,则要安装mssql模块 安装模块的执行命令为 pip install pymssqlimport pymssql def ExecuteNonQuery(sqlStr,paras):    try:        connect = pymssql.connect(server='192.168.1.23:1......
  • jmeter 连接 sqlserver 数据库
    1. 将下载好的 jar 包放在 jmeter/lib 目录下,测试计划中导入 jar 包 2.添加 JDBCConnectionConfiguration 配置,参数设置如下 3. 线程组中添加 JDBCRequest,请求中连接名字(test)和步骤2 中自定义的名字(test)保持一致 4. 点击运行,查询出对应的结果 问题回......
  • mybatis一级缓存
    一级缓存默认:mybatis默认开启一级缓存,且local-cache-scope默认session级别,默认关闭二级缓存配置如下:mybatis关闭二级缓存,一级缓存作用范围为session;查询语句也没有使用flushCache=“true”(默认false)==>所以使用了缓存,但是这样直接操作缓存对象,下一次查询出来的是被修改后的......
  • IFreeSql各数据库连接字符串格式
    IFreeSql各数据库连接字符串格式 DataTypeConnectionStringDataType.MySqlDataSource=127.0.0.1;Port=3306;UserID=root;Password=root;InitialCatalog=cccddd;Charset=utf8;SslMode=none;Minpoolsize=1DataType.PostgreSQLHost=192.168.164.10;Port=5432;Us......
  • 详解K8s 镜像缓存管理kube-fledged
    本文分享自华为云社区《K8s镜像缓存管理kube-fledged认知》,作者:山河已无恙。我们知道 k8s 上的容器调度需要在调度的节点行拉取当前容器的镜像,在一些特殊场景中,需要快速启动和/或扩展的应用程序。例如,由于数据量激增,执行实时数据处理的应用程序需要快速扩展。镜像比......
  • Go实践:用Sync.Map实现简易内存缓存系统
    介绍定义了一个Cache结构体,其中使用sync.Map作为底层数据结构来存储缓存项。Set方法用于设置缓存项,指定键、值以及过期时间。Get方法用于获取缓存项,如果缓存项存在且未过期,则返回值和true,否则返回nil和false。方法的接受者为指针类型,是为了对Cache对象进行操作,并在方法内部访问和......
  • 汇编语言简易教程(13):栈缓存溢出
    汇编语言简易教程(13):栈缓存溢出当程序溢出基于堆栈的动态变量时,可能会发生堆栈缓冲区溢出。例如,如果一个程序分配并使用一个基于堆栈的本地数组,该数组包含50个元素,并且数组中存储了50个以上的元素,则会发生溢出。这种溢出通常是坏的,通常会导致程序错误,甚至可能使程序崩溃......