1.mysql与redis如何实现数据同步问题?
2.什么是缓存延迟双删策略
3.为什么不建议使用延迟双删
4.先删除缓存,在更新db还是?先更新db在删除缓存?
5.什么是双写一致性协议
6.并发的情况下如何保证双写一致性问题
7.分布式锁如何解决双写一致性问题
8.mysql行锁机制如何解决双写一致性问题
9.为何说分布式情况下强一致性几乎很难实现
作者:余胜军 微信:yushengjun644 QQ644064065
该内容为原创,未经过允许的情况下 请勿转载!
在高并发的情况下,我们会使用Redis缓存减轻数据库访问压力,先查询缓存,如果缓存中没有数据在查询数据库,将数据库的数据在缓存到Redis中,如果db数据发生了更新,需要同步到Redis中,同步的过程中需要保证:MySQL与Redis数据一致性问题,mysql与redis数据同步过程中,需要花费短暂的时间,所以会产生短暂的延迟,属于正常现象。
一致性情况:如果缓存中有数据,需要与MySQL中数据保持一致性;
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;
伪代码:
deleteMayiktRedis(key);// 先删除缓存
updateDB(user);// 更新db中的数据
1.如果A请求删除缓存成功,但是更新DB失败呢,导致Redis与mysql数据一致性,另外请求过来有从新查询db数据老的数据放入到缓存中;
解决办法:
1.采用MQ异步的形式,先删除缓存在更新db中的数据,MQ消费者如果消费失败了,MQ服务器端自动
触发重试策略,保证该msg必须消费成功。
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);// 先删除缓存
t1线程 先更新db;
t2线程查询命中缓存 返回旧的数据;
t1线程在删除缓存
假设t1线程更新完db,预计5毫秒删除完缓存key 在5毫秒内 其他线程查询缓存结果还是为旧的数据,但是
5毫秒后查询缓存结果是为空,在从新将db最新的结果同步到Redis中。
分布式数据同步的过程中,延迟是非常正常的,所以该情况发生的延迟对业务的影响其实很小。
但是如果发生了,删除缓存失败呢?
1.不断重试----如果是在http协议接口中 会导致接口响应变慢 调用该接口 会发生响应超时—
2.或者通过mq异步的形式同步
确保缓存删除成功。
updateMayiktDB(user);// 更新db中的数据
deleteMayiktRedis(key);// 先删除缓存
绝大多数场景都会将Redis作为只读缓存:
1.先删除缓存值再更新数据库—更新db完成之后 延迟双删策略 延迟时间多久
是很难控制的。
2.也可以先更新数据库再异步 mq消费者删除缓存;----推荐
3.因为先更新db,在删除缓存,其他线程在读过程中只是短暂读取到数据是旧数据,只要及时的将该缓存key删除,其他线程就可以读取到最新的数据。
4.而先删除缓存,在更新db 有可能将旧的数据缓存到Redis中,导致其他线程一直查询的数据是为旧的数据,
需要考虑延迟双删问题,延迟双删时间不是很好控制。
所以推荐使用 先更新,在删除缓存。
什么是双写?
updateDB
updateRedis
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 行锁形式
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异步 双写 没有锁的竞争效率比较高 —延迟问题