在高并发的业务场景下,数据库的性能瓶颈往往都是用户并发访问过大。所以,一般都使用redis做一个缓冲操作,让请求先访问到redis,而不是直接去访问MySQL等数据库。从而减少网络请求的延迟响应
数据为什么会不一致
这样的问题主要是在并发读写访问的时候,缓存和数据相互交叉执行。
一、单库情况下
同一时刻发生了并发读写请求,例如为A(写) B (读)2个请求
A请求发送一个写操作到服务端,第一步会淘汰cache,然后因为各种原因卡主了,不在执行后面业务(例:大量的业务操作、调用其他服务处理消耗了1s)。
B请求发送一个读操作,读cache,因为cache淘汰,所以为空
B请求继续读DB,读出一个脏数据,并写入cache
A请求终于执行完全,在写入数据到DB
总结:因最后才把写操作数据入DB,并没同步。cache里面一直保持脏数据
脏数据是指源系统中的数据不在给定的范围内或对于实际业务毫无意义,或是数据格式非法,以及在源系统中存在不规范的编码和含糊的业务逻辑。
二、主从同步,读写分离的情况下,读从库而产生脏数据
A请求发送一个写操作到服务端,第一步会淘汰cache
A请求写主数据库,写了最新的数据。
B请求发送一个读操作,读cache,因为cache淘汰,所以为空
B请求继续读DB,读的是从库,此时主从同步还没同步成功。读出脏数据,然后脏数据入cache
最后数据库主从同步完成
总结:这种情况下请求A和请求B操作时序没问题,是主从同步的时延问题(假设1s),导致读请求读取从库读到脏数据导致的不一致
根本原因:
单库下,逻辑处理中消耗1s。可能读到旧数据入缓存
主从+读写分离,在1s的主从同步时延中。读到从库的旧数据入缓存
数据优化方案
一、缓存双淘汰法
-
先淘汰缓存
-
再写数据库
-
往消息总线esb发送一个淘汰消息,发送立即返回。写请求的处理时间几乎没有增加,这个方法淘汰了缓存两次。因此被称为“缓存双淘汰法“,而在消息总线下游,有一个异步淘汰缓存的消费者,在拿到淘汰消息在1s后淘汰缓存,这样,即使在一秒内有脏数据入缓存,也能够被淘汰掉。
二、异步淘汰缓存
上述的步骤,都是在业务线里面执行,新增一个线下的读取binlog异步淘汰缓存模块,读取binlog总的数据,然后进行异步淘汰。
1.思路:
MySQL binlog增量发布订阅消费+消息队列+增量数据更新到redis
1)读请求走Redis:热数据基本都在Redis
2)写请求走MySQL: 增删改都操作MySQL
3)更新Redis数据:MySQ的数据操作binlog,来更新到Redis
2.Redis更新
1)数据操作主要分为两块:
-
一个是全量(将全部数据一次写入到redis)
-
一个是增量(实时更新)
这里说的是增量,指的是mysql的update、insert、delate变更数据。
这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。就无需在从业务线去操作缓存内容
如有感悟,欢迎关注和交流探讨额
php redis做mysql的缓存,怎么异步redis同步到mysql数据库?
此时一位IT码农路过,并留下个人见解。
要想redis异步去同步Mysql的数据,大部分时候使用的都是队列的形式。例如php使用resque包进行部署,实现自动化队列的形式,开一些额外的线程监听,将一些操作push到队列上,然后被监听之后就执行相关的操作(某个控制器和方法)同步到Mysql表里面。
或者是采用swoole扩展,里面有封装redis的异步操作,可以很容易的实现redis的异步,然后再把数据同步到mysql上。
最后,喜欢的小伙伴可以给我点赞或者关注我哦。
"
项目中用到了Redis做缓存,如何保证数据库和缓存数据的一致性?
先举两个例子,都是我做过的项目,应用场景都非常简单:
-
曾经做过的一个保费试算的项目,业务场景是根据用户基本信息+费率做计算,得到一个保费值,如果不了解这个业务场景的话,可以看做【用户输入】+【数据库数据】+【一定的计算】=【结果】;这些数据复杂而且多,使用频繁,但是每个产品的费率数据,几乎是不会改变的(新增修改删除都不会有):这些数据被我们放在Redis中,而且设置成永不失效;因为数据不会修改,那么数据库和缓存中的数据肯定是一样的。
-
现在做的项目中的一个功能,简单描述业务场景:系统从N个业务系统中抽取数据,并做加工整合,加工完成后更新到数据库中;这个场景也比较有特点,数据时效性要求很低(数据加工到数据库已经晚了一天),数据变化的时间固定且唯一(批处理每天固定时间进行,只有一个批处理线程在跑);于是我们采用了一个比较保守的策略,就是设置数据在Redis中的过期时间;等数据过期后,当查询的时候发现数据不在缓存中,再从数据库中查询出来后放入缓存。
现在想一想Redis中数据的更新策略有哪些(或者说缓存更新策略)?
- 给缓存设置过期时间,是我认为比较好的方式(这里不讨论缓存雪崩的问题),这个【比较好】是在实现难度和数据一致性之间找到了一个平衡点;实现起来非常简单,但是可能在数据变化后,缓存失效前的这段时间,数据库和缓存之间的数据是不一致的(但最终会一致)。
-
先更新数据库,再更新缓存:有两个比较大的问题,一是多线程的时候,可能会造成数据库和缓存数据的不一致,而且这个不一致可能会是长期的。二是如果更新操作比较多的话,会频繁地更新缓存。
- A线程:set 数据库 key=1;
- B线程:set 数据库 key=100;
- B线程:set 缓存 key=100;
- A线程:set 缓存 key=1;(实际上,应该等于100,这样就造成了缓存和数据库中的数据不一致,并且如果数据不再更新,那么会使长期不一致)
-
先删缓存,再更新数据库:这个会出现问题,还是A/B两个线程,如果A删除了缓存之后,在更新数据库之前,B过来查询不到缓存,于是就查询了数据库(A还没更新),把数据写入缓存,这时候A才把数据库更新掉,这可能就会造成永久性的不一致。
-
先更新数据库,再删缓存:一样的道理,大家可以自己想一想这个过程。
有些人可能觉得这个概率很小,怎么可能有这么巧的事情,恰好都是A做到一半,B插进来把全部的事情做完,A再去做后一半的操作;但是概率小,也是可能发生的,不得不考虑。
还有很多复杂的实现方案,比如更新完数据库之后,把更新操作发给有序的消息队列(不过过程越复杂,不一致的时间会越长),由另外的线程从队列里面顺序执行更新或删除缓存的操作;
不过我认为,架构越复杂也越容易出错,还不如根据实际的场景和团队的开发水平,合理选择缓存的使用,并给缓存设置合理的过期时间。