缓存一致性坑【done】

1 写db-delete redis

 

保可用性,这个方案不行,必须加入双删去扰动并发

 

保一致性(也不行,必须加select数据库锁与隔壁update互斥,redis分布式锁不行

 

 

  线程1 线程2 查询 线程3 线程2 redis包裹在数据库锁 线程3 redis包裹在数据库锁
 

update db

commit

       
  delete redis  

 

 

   
    query db 可能旧值 update db

情况1(查询先于线程3):

开启只读事务,加锁

使线程3阻塞

set redis(旧值包裹在锁里

结束事务,线程3获得锁

update db 
      commit 情况2:

开启只读事务,加锁,阻塞等待线程3提交

线程3commit,线程2获得锁并查询到新值

delete redis

commit

使线程2的set redis永远比线程3的delete晚

    卡网或jvm停顿 delete redis    
    set redis      
      延时再删    

坑!

根本困难:

1 update db与delete redis不是原子(没有数据一致性损失,被人插一脚进来没事)

2 query db与set redis不是原子(select无锁下有数据一致性损失),select与set之间,新值诞生

不能有下情况:

2 1
query  
  update
  delete
set  

 

不加锁

select 第二条update
发现null,查库,查到旧值 update
  提交,delete redis
set redis 旧值  

因为查和写非原子,被人插一脚进来

所以2必须原子

 

 

加锁

1)2原子1不原子

2先于1

2 select 1 第二条update
发现null,查库,加锁成功 update阻塞
set redis,提交事务  
  获得锁,提交,delete redis
 脏数据被delete,good  

 2晚于1

2 select 1 第二条update
发现null,查库,加锁,阻塞 update 获取锁
  提交
获得锁 set redis 提交事务 delete redis(在锁外
无论delete与set的先后,都不会有脏数据  

 2)2原子1原子

2先于(同1)

2 select 1 第二条update
发现null,查库,加锁成功 update阻塞
set redis,提交事务  
  获得锁,提交,delete redis
 脏数据被delete,good  

 2晚于1

2 select 1 第二条update
发现null,查库,加锁,阻塞 update 获取锁
  delete redis,提交
获得锁 set redis 提交事务  
与1)相比,性能增加了  

核心解决方案:

使 1 与 2 在关系到某一条记录时,让他们2原子且有序

 

结论:

强一致性-倒不如写完db直接写redis,开事务使set redis有序,同时承担commit本身分布式事务不一致风险,commit异常时删除redis

强一致性(只要数据更新了db,隔壁一定要读取到新值)

分布式锁防止多个线程打到数据库,select加数据库锁开启只读事务且路由到主库,set redis包裹在事务中

 

原则上数据库锁事务里不应该有redis或mq的io

 

弱一致性

1)延迟再删扰动并发,但不是100%的,虽然可能性很小

双删的本质,增加扰动减小并发失误概率,尤其在读者分离场景,并以持续的双删推动缓存持续更新,不一定要一步到位更新到最新值,但最大程度避免旧值停滞,双删适用时间轮

2 1
query  
  update
  delete
它还就卡了2秒,比如等连接池 等2s再次delete
set  

那么读写分离的db场景呢?

2 1 另一个2 db集群
query 从      
  update 主    
  delete    
set 等2s再次delete   就看2秒主到从同步有没有完成
  delete query 从  
    set 旧值  

 

弱一致性方案仍然存在一定可能出现缓存值停滞,应优先使用canal

 

2) canal,订阅主或从库binlog

 

 
2 mq

db A操作早于B,然而刷缓存操作A晚于B;同时多线程下消息有序性几乎是不可能的

刷db时主线程可以强行刷redis,但是需要结合业务保护有序性,但是很难 除非分布式锁(比如db事务)

即使利用update本身加锁性质,从而在commit前发送mq,但仍然不能保证mq投递的有序性(比如网络抖动),除非在producer端业务层确保有序,比如commit前生成递增no再组织投递,这个操作可以被信任(因为没有网络) 

然后投递到同一个partition,消费者检验no,如果下一个no比上一个还小,则丢弃(视具体业务);如果顺序则刷redis;

 

这个方案其实没有意义,如果我都能确保多线程mq有序性了,直接写redis不是更好吗,属于脱裤子放屁

 

posted on 2025-08-02 22:18  silyvin  阅读(16)  评论(0)    收藏  举报