前言

当前的应用服务很多都有着高并发的业务场景,对于高并发的解决方案一般会用到缓存来降低数据库压力,并且还能够提高系统性能减少请求耗时,比如我们常用的redis缓存。当然,一般业务在更新要刷新缓存,但是如果采用的方式方法不对是会造成数据不一致的情况,让用户拿到错误的缓存数据。今天,就来讨论一下缓存与数据不一致的原因并提供解决方案。

在这里插入图片描述

技术积累

查询缓存业务流程

在这里插入图片描述

查询步骤:
1、请求打到服务端首先到redis查看是否存在缓存
2、存在缓存直接返回前端,不存在直接查询数据库
3、查询数据库存在写入缓存并返回,不存在则写入布隆过滤器返回

注意:在写入缓存的步骤需要加上分布式锁并设置未获取到锁立即失败的策略,以防止高并发下多个线程争抢资源的情况。

为什么数据库没有会写入布隆过滤器?

主要用来解决redis缓存击穿。

布隆过滤器的主要原理是使用一组哈希函数,将元素映射成一组位数组中的索引位置。当要检查一个元素是否在集合中时,将该元素进行哈希处理,然后查看哈希值对应的位数组的值是否为1。如果哈希值对应的位数组的值都为1,那么这个元素可能在集合中,否则这个元素肯定不在集合中。

更新缓存业务流程

在这里插入图片描述

以上是常规的新增缓存的业务流程,其步骤是:
1、业务更新数据
2、刷新缓存

更新缓存问题

以上更新缓存有什么问题?

如果并发量不高的情况下,可能用户并不会发现缓存与数据库不一致的情况。

但是在高并发的情况下,A线程新增数据后还没有来得及刷新缓存,线程B又修改了数据库数据并刷新了缓存,此时线程A由于运行太慢才拿着A的数据去刷新缓存,这样就导致redis数据是A线程的数据与数据库B线程数据不一致。

当然,有的同学可能会采用先删除缓存后更新业务数据库的策略,比如线程A刚刚删除了缓存还没有更新数据库,线程B访问了就立即会讲数据库数据加入缓存并返回,然后A线程继续更新业务数据库。导致redis数据是B线程数据与数据库A线程数据不一致。

所以,如果你的业务系统对缓存敏感就必须要解决缓存不一致的问题。

解决方案

1、redis延迟双删
在这里插入图片描述

1.1 具体的操作步骤:
先删除缓存;
再写数据库;
休眠300毫秒 休眠时间根据自身业务情况进行增减;
再次删除缓存。

1.2 设置缓存过期时间
从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。

方案的缺点
结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。

2、异步更新缓存(基于订阅binlog的同步机制)

在这里插入图片描述

2.1 方案一手动监听mysql binlog+消息队列mq
项目启动立即将缓存全量加入redis;
我们不再关心业务数据跟新,直接加上事务写入mysql数据库即可,此时mysql会产生binlog日志;
mq生产者监听mysql binlog,发现binlog变动将具体的日志发送到mq相关队列
mq消费者监听到有消息立即消费,并解析出具体的数据调用redis更新

2.2 方案二开源中间件canal
其实canal的思路与方案一差不多,不同的是canal将自己伪装成一个mysql slave从节点。

大家应该都知道mysql主从同步的原理:
当mysql slave连接到master节点时,master会开一个binlog dump线程来监听binlog变化,slave也会开启一个IO线程。当有数据变更的时候,binlog dump线程就会将日志名字和同步位置等信息发送给slave IO线程,slave IO线程会根据接收到的同步信息去master拉取binlog并保存到自己的relay log中继日志中。这个时候slave节点的sql 线程会不断地从relay log中继日志将数据写入数据库。

具体步骤:
canal的服务端启动伪装为mysql slave注册到mysql 集群
canal服务端拿到binlog日志后会进行解析和封装为带有DML语句的对象数据
canal服务端创建线程将封装好的对象数据发送到消息队列mq
canal客户端会监听具体的消息队列,获取到具体的DML语句进行解析
canal客户端创建线程对redis进行操作

写在最后

在高并发的业务场景下redis与mysql数据库非常容易产生数据不一致的情况,我们可以采用redis缓存延迟双删除策略达到数据的最终一致性,也可以采用一部缓存更新自定义监听mysql binblog和采用canal开源中间件实现缓存的实时一致性方案。总的来说,都是比较简单的,而且都能够达到良好的效果。

相关文章

Redis高并发分布锁实战

Redis分布式锁自己去实现可能会出现几个问题没有在finally显示释放锁,当客户端挂掉了,锁没有被及时删除,这样会导致死锁问题,它这个是需要我们显示的释放锁假如此时我们设置过期时间,但是我们用的是同一个key,就可能出现下一个线程删除上一个线程的锁,但是上一个线程还没有执行完,它这个需要key是不能重复的假如我们既设置了过期时间也指定了不同的key,此时可能因为网络延迟出现上一个线程删除下一个线程的锁,也就是说业务执行的时间超过了锁过期的时间,它这个需要一个锁续命的功能。

Redis是否为单线程?

在深入讨论Redis是否为单线程之前,我们先来了解一下Redis的基本架构。Redis采用了基于内存的数据存储方式,数据存储在内存中,并通过持久化机制将数据定期写入磁盘。客户端:与Redis进行通信的应用程序。Server:负责处理客户端请求、执行命令和管理数据。数据结构:Redis支持多种数据结构,如字符串、列表、哈希表等。事件处理器:用于处理网络事件和命令请求。

Git如何清除缓存?这四个命令得会!

git rm适用于从版本库和工作目录中删除文件,但无法处理未被追踪文件。git reset可以用于回滚提交和从缓存中移除文件,但不涉及工作目录中未被追踪的文件。提供更高级的索引控制,但不直接删除文件。git clean用于删除工作目录中未被追踪的文件。在实际使用中,根据具体的需求和场景选择适当的命令。清除缓存是一项破坏性操作,需要慎重对待,确保在清除之前做好备份,并尽量避免不必要的清除操作。

MySQL中的高级查询

通过条件查询可以查询到符合条件的数据,但如同要实现对字段的值进行计算、根据一个或多个字段对查询结果进行分组等操作时,就需要使用更高级的查询,MySQL提供了聚合函数、分组查询、排序查询、限量查询、内置函数以实现更复杂的查询需求。接下来将针对这些高级查询的知识进行讲解。

ubuntu20.04安装实时内核补丁PREEMPT_RT

下载实时内核补丁,我下载patch-5.15.148-rt74.patch.sign和patch-5.15.148-rt74.patch.xz。通过以下指令看具体报错并输出日志到make.log:make -j1 deb-pkg 2>&1 | tee ~/make.log。比较幸运没遇到问题,重启进入后,启动页面没有变化,还是进入ubuntu,但是查看内核版本已经自动变到5.15.148。我下载linux-5.15.148.tar.xz和linux-5.15.148.tar.sign。

日常遇到Maven出现依赖版本/缓存问题通用思路。

如果怀疑是本地仓库中缓存的依赖有问题,可以手动删除本地仓库(默认位置在用户的.m2/repository目录下),但这是一个较为极端的做法,因为这会删除所有项目的所有本地依赖,之后Maven将不得不重新下载这些依赖。针对于这样的问题 首先我们的第一思路 就是怀疑到是缓存的问题,那么我在这里去描述一下 我们遇到这类通用类的问题如何解决。检查项目的pom.xml文件,确认依赖声明正确无误,没有冲突的版本号或不正确的依赖范围。版本问题导致的,但是我确认过了一下的一些操作 依然没有解决我的问题。

mysql中文首字母排序查询

MySQL中的排序涉及到字符集和排序规则。默认情况下,MySQL按照ASCII码对字符进行排序,数字>字母>中文。但是,特殊字符(非字母、数字、中文)的排序需要一些额外处理。匹配到非字母数字中文的内容,做排序,字母数字中文为null,排序优先级最高,排在上面。为什么用HEX()函数做十六进制编码?因为中文用常规的正则不能匹配到结果。试过SUBSTRING、LEFT等,都不能完美实现多中文的首字母排序。为什么要把字母数字中文放在一起匹配?因为处理复杂度会更高。这样可以处理更复杂的排序需求。

使用redis-insight连接到服务器上的redis数据库

我们现在虽然安装好了redis数据库,但是外界是连接不到的,我们需要打破这个限制!设置完之后,可以按以下图的命令查看,redis的密码是不是起作用了。的更改,并退出编辑器。在网上下载好redis-insight的客户端,打开。默认情况下,它可能被设置为只监听本地连接,如。这允许在没有进行身份验证的情况下接受外部连接。(3)为了增强安全性,强烈建议设置访问密码。三、使用redis-insight连接数据库。1.查找redis的配置文件。指令,并确保将其设置为。替换为你自己的强密码。

linux docker 部署mysql8以上版本时弹出Access denied for user root @ localhost (using password: YES)的解决方案

mysql8登录第一次遇到MYSQL_ROOT_PASSWORD时会自动把该密码尽兴登录,生成一个秘钥放在mysql的数据文件里面,命令里带的MYSQL_ROOT_PASSWORD密码是个参数,除了第一次运行mysql带上会设置密码生成秘钥,其他次启动而不是设置mysql的密码,而是作为参数去验证这个最初的秘钥是否核对正确,于是我进入挂载的data目录,发现我的猜想是对的。通过docker将服务部署完后,navicat连接报错,密码错误,于是我尝试进入mysql容器登录 发现也报错。

数据湖Paimon入门指南

如果用户建表时指定'merge-engine' = 'partial-update',那么就会使用部分更新表引擎,可以做到多个 Flink 流任务去更新同一张表,每条流任务只更新一张表的部分列,最终实现一行完整的数据的更新,对于需要拉宽表的业务场景,partial-update 非常适合此场景,而且构建宽表的操作也相对简单。这种方式的成本相对较高,同时官方不建议这样使用,因为下游任务在 State 中存储一份全量的数据,即每条数据以及其变更记录都需要保存在状态中。流式查询将不断产生最新的更改。

基于SQL数据库的大模型RAG实现

检索增强生成 (RAG) 涉及从外部数据库获取当前或上下文相关信息,并在请求大型语言模型 (LLM) 生成响应时将其呈现给大型语言模型 (LLM) 的过程。这种方法有效地解决了生成不正确或误导性信息的问题。你能够存储专有业务数据或全局知识,并使你的应用程序能够在响应生成阶段为 LLM 检索此数据。

MySQL运行在docker容器中会损失多少性能

自从使用docker以来,就经常听说MySQL数据库最好别运行在容器中,性能会损失很多。一些之前没使用过容器的同事,对数据库运行在容器中也是忌讳莫深,甚至只要数据库跑在容器中出现性能问题时,首先就把问题推到容器上。

Mysql大数据量分页优化

之前有看过到mysql大数据量分页情况下性能会很差,但是没有探究过它的原因,今天讲一讲mysql大数据量下偏移量很大,性能很差的问题,并附上解决方式。

oracle data block , extent 和segment区别

总结来说,Data block是数据库中最小的逻辑存储单位,用于存储实际的数据记录;Extent是由若干个连续的Data blocks组成的区域,表示一段连续的存储空间;data block是数据库中最小的逻辑存储单元。当数据库的对象需要更多的物理存储空间时,连续的data block就组成了extent . 一个数据库对象拥有的所有extents被称为该对象的segment.Data block、extent和segment是数据库中不同层次的数据存储和管理单位,它们各自具有不同的功能和特点。

MySQL数据库主从复制集群原理概念以及搭建流程

主从复制是指将主数据库的 DDL 和 DML 操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。MySQL支持一台主库同时向多台从库进行复制, 从库同时也可以作为其他从服务器的主库,实现链状复制。主库出现问题,可以快速切换到从库提供服务。实现读写分离,降低主库的访问压力。可以在从库中执行备份,以避免备份期间影响主库服务。

【.NET Core】Lazy<T> 实现延迟加载详解

延迟初始化是一种将对象的创建延迟到第一次需要用时的技术。简而言之,就是对象的初始化发生在第一次需要调用的时候执行。通常所说的延迟初始化和延迟实例化的意思是相同。通过使用延迟基础,可以避免应用程序不必要的计算和内存消耗。从.NET 4.0开始,可以使用Lazy来实现对象的延迟初始化,从而优化系统的性能。延迟初始化就是将对象的初始化延迟到第一次使用该对象时。延迟初始化是我们优化程序性能的一种方式。如创建一个对象时需要花费很大的开销,而这一对象在系统运行过程中不一定会用到。

CentOS本地部署SQL Server数据库无公网ip环境实现远程访问

GeoServer是OGC Web服务器规范的J2EE实现,利用GeoServer可以方便地发布地图数据,允许用户对要素数据进行更新、删除、插入操作,通过GeoServer可以比较容易地在用户之间迅速共享空间地理信息。另外,GeoServer是开源软件。下面介绍GeoServer web ui 管理界面 结合cpolar 内网穿透工具实现远程访问,

[redis] redis的安装,配置与简单操作

Redis是一个开源、基于内存、使用C语言编写的key-value数据库,并提供了多种语言的API。它的数据结构十分丰富,主要可以用于数据库、缓存、分布式锁、消息队列等...Redis服务器程序是单进程模型,也就是在一台服务器上可以同时启动多个Redis进程,Redis的实际处理速度则是完全依靠于主进程的执行效率。若在服务器上只运行一个Redis进程,当多个客户端同时访问时,服务器的处理能力是会有一定程度的下降;

Redis的IO多路复用原理解析

模拟一个tcp服务器处理30个客户socket,一个监考老师监考多个学生,谁举手就应答谁。假设你是一个监考老师,让30个学生解答一道竞赛考题,然后负责验收学生答卷,你有下面几个选择:第一种选择:按顺序逐个验收,先验收A,然后是B,之后是C、D。。。这中间如果有一个学生卡住,全班都会被耽误,你用循环挨个处理socket,根本不具有并发能力。第二种选择:你创建30个分身线程,每个分身线程检查一个学生的答案是否正确。这种类似于为每一个用户创建一个进程或者线程处理连接。

在 Docker 中配置 MySQL 数据库并初始化 Project 项目

这样,您就完成了在 Docker 中配置 MySQL 数据库并初始化 Project 项目的过程。希望这篇博客对您有所帮助!创建目录 /project/mysql 以及 /project/mysql_data。在每个 SQL 文件中,将 AUTO_INCREMENT 修改为 1。将准备好的 SQL 文件复制到 /project/mysql 目录。将 init.sql 放到 /project/mysql 目录。在 SQL 文件中插入管理员相关数据。在 SQL 文件中插入机型相关数据。1.4. 插入管理员。

深入理解Mysql事务隔离级别与锁机制

我们的数据库一般都会并发执行多个事务,多个事务可能会并发的对相同的一批数据进行增删改查操作,可能就会导致我们说的脏写、脏读、不可重复读、幻读这些问题。这些问题的本质都是数据库的多事务并发问题,为了解决多事务并发问题,数据库设计了事务隔离机制、锁机制、MVCC多版本并发控制隔离机制,用一整套机制来解决多事务并发问题。接下来,我们会深入讲解这些机制,让大家彻底理解数据库内部的执行原理。
返回
顶部