1.mysql与redis如何实现数据同步问题?
2.什么是缓存延迟双删策略
3.为什么不建议使用延迟双删
4.先删除缓存,在更新db还是?先更新db在删除缓存?
5.什么是双写一致性协议
6.并发的情况下如何保证双写一致性问题
7.分布式锁如何解决双写一致性问题
8.mysql行锁机制如何解决双写一致性问题
9.为何说分布式情况下强一致性几乎很难实现

20点25准时开始

概述

作者:余胜军 微信:yushengjun644 QQ644064065

该内容为原创,未经过允许的情况下 请勿转载!

在高并发的情况下,我们会使用Redis缓存减轻数据库访问压力,先查询缓存,如果缓存中没有数据在查询数据库,将数据库的数据在缓存到Redis中,如果db数据发生了更新,需要同步到Redis中,同步的过程中需要保证:MySQL与Redis数据一致性问题,mysql与redis数据同步过程中,需要花费短暂的时间,所以会产生短暂的延迟,属于正常现象。

一致性情况:如果缓存中有数据,需要与MySQL中数据保持一致性;

img

String value=getRedisKey(key);//先查询Redis 如果redis中有数据,则不会查询db
if(!StringUtils.isEmpty(value)){
  return value;
}
// 如果redis缓存没有数据,则查询db,将db中的数据放入缓存中。
UserDo userDb=userMapper.getUser(userId);
if(userDb==null){
  return null;
}
RedisUtils.set(key,userDb); // 将db数据放入到缓存中
return userDb;

mysql与redis数据如何保证一致性?

延迟双删策略

先删除缓存,在更新DB

无并发的情况

伪代码:

deleteMayiktRedis(key);// 先删除缓存
updateDB(user);// 更新db中的数据

1.如果A请求删除缓存成功,但是更新DB失败呢,导致Redis与mysql数据一致性,另外请求过来有从新查询db数据老的数据放入到缓存中;

解决办法:

1.采用MQ异步的形式,先删除缓存在更新db中的数据,MQ消费者如果消费失败了,MQ服务器端自动

触发重试策略,保证该msg必须消费成功。

高并发的情况

img

1.t1线程先删除缓存;

2.t2线程读取缓存为null,同步db数据到缓存中;

3.t1线程更新db中的数据;

4.t3线程查询缓存中数据是旧数据;

如何解决该问题:延迟双删策略

t1线程更新完DB后,让它sleep一段时间,再删除缓存 ;

---------延迟双删 谁提出的?不靠谱?

为何要sleep一段时间,再删除缓存?

T2线程如果读取到DB旧的数据,再把旧的数据写入缓存,然后,T1线程延迟再进行删除。
所以,T1 sleep的时间,就需要大于T2读取数据再写入缓存的时间。

延迟的时间如何确定?

在业务程序运行时,统计业务逻辑执行读数据和写缓存的操作时间,以此为基础来进行估算。因为这个方案会在第一次删除缓存值后,延迟一段时间再次进行删除,所以称为“延迟双删”。----不推荐大家使用延迟双删

deleteMayiktRedis(key);// 先删除缓存
updateMayiktDB(user);// 更新db中的数据
Thread.sleep(u);// 延迟一段时间,在删除该缓存key
deleteMayiktRedis(key);// 先删除缓存

先更新DB,在删除缓存

img

t1线程 先更新db;

t2线程查询命中缓存 返回旧的数据;

t1线程在删除缓存

假设t1线程更新完db,预计5毫秒删除完缓存key 在5毫秒内 其他线程查询缓存结果还是为旧的数据,但是

5毫秒后查询缓存结果是为空,在从新将db最新的结果同步到Redis中。

分布式数据同步的过程中,延迟是非常正常的,所以该情况发生的延迟对业务的影响其实很小。

但是如果发生了,删除缓存失败呢?

1.不断重试----如果是在http协议接口中 会导致接口响应变慢 调用该接口 会发生响应超时—

2.或者通过mq异步的形式同步

确保缓存删除成功。

img

updateMayiktDB(user);// 更新db中的数据
deleteMayiktRedis(key);// 先删除缓存

方案如何选择?

绝大多数场景都会将Redis作为只读缓存:

1.先删除缓存值再更新数据库—更新db完成之后 延迟双删策略 延迟时间多久

是很难控制的。

2.也可以先更新数据库再异步 mq消费者删除缓存;----推荐

3.因为先更新db,在删除缓存,其他线程在读过程中只是短暂读取到数据是旧数据,只要及时的将该缓存key删除,其他线程就可以读取到最新的数据。

4.而先删除缓存,在更新db 有可能将旧的数据缓存到Redis中,导致其他线程一直查询的数据是为旧的数据,

需要考虑延迟双删问题,延迟双删时间不是很好控制。

所以推荐使用 先更新,在删除缓存。

双写一致性协议原理

什么是双写?

updateDB

updateRedis

直接更新 Cache

1.更新完db后,同步更新Redis; 更新db操作与更新redis操作 是一样的 不是 直接删除Redis缓存key 俗称:双写

例如

1.updateMayiktDB(user);
2.updateMayiktRedis(user);
1.deleteMayiktDB(user);
2.deleteMayiktRedis(user);
1.insertMayiktDB(user);
2.insertMayiktRedis(user);

先更新完db后,在同步到redis的过程中会存在短暂的延迟, 影响的业务其实很小。

但是如果在并发的情况下做双写会存在同步Redis数据不一致问题。

并发情况下如何保证双写一致性

概述:

在多个线程同时修改db和redis;

在db 层面 mysql 行锁机制 多个线程 同时修改同一行数据

最终只会有一个线程能够修改成功;在底层存储引擎层面

保证数据线程安全性问题。

例如:

t1 先执行 updatedb name=xiaowei

t2 在执行 updatedb name=xiaojun

最终db层面 name=xiaojun

t1和t2同时更新Redis

t2线程 先执行 update redis name=xiaojun

在执行t1 线程 update redis name=xiaowei

db层面 name=xiaojun Redis name=xiaowei

如何解决?锁的形式解决

1.分布式锁的形式 ----不推荐 效率很低 最终 代码执行 变成单线程。

2.通过mysql 行锁形式

img

t1和t2线程 同时对db数据做修改操作,同时需要更新redis缓存。

但是在同时更新redis缓存过程中 ,会存在顺序执行问题,导致数据不一致。

begin;
t1线程
updatemayiktDb(name)---mayikt
updatemayiktRedis(name)---mayikt
commit/rollback
begin;
t2线程
updatemayiktDb(name)---xiaojun
updatemayiktRedis(name)---xiaojun
commit/rollback

解决办法:

1.分布式锁解决多个线程同时执行双写业务逻辑,最终只会有一个获取到分布式锁线程才可以执行,没有获取到分布式锁线程则阻塞等待,这样确保线程执行双写 不会被其他线程干扰,但是效率非常低;

2.mysql 行锁实现 ,多个线程同时获取行锁,最终只会有一个线程获取行锁成功,

获取行锁成功的线程 如果更新Redis 成功,就可以提交事务,如果更新redis 失败

可以采用重试策略,重试多次更新到redis还是失败的话,直接回滚事务,同时释放行锁,行锁-----提交或者回滚事务。 但是效率非常低;不推荐

mysql 数据同步到Redis----该网络传输的过程中 需要花费时间

redis 与mysql 如何保证数据一致性问题

1.先删除缓存,在更新db-----延迟双删的 不推荐

2.先更新db,在删除缓存----mq 确保删除缓存 一定成功

3.双写一致性协议 并发的情况下 结合锁的形式 避免

数据同步到redis发生了不一致。

4.canal+kafka+消息顺序一致性—

推荐mq异步 双写 没有锁的竞争效率比较高 —延迟问题

GitHub 加速计划 / ca / canal
28.22 K
7.57 K
下载
alibaba/canal: Canal 是由阿里巴巴开源的分布式数据库同步系统,主要用于实现MySQL数据库的日志解析和实时增量数据订阅与消费,广泛应用于数据库变更消息的捕获、数据迁移、缓存更新等场景。
最近提交(Master分支:3 个月前 )
1e5b8a20 - 2 个月前
ff82fd65 2 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐