并发线程读取Redis和数据库一致性问题
一致性问题出现背景
当数据发生update和delete时,需要同时更新Redis和数据库,我们先改数据库再删缓存。但这两个写操作不满足原子性,任一操作失败都会导致两者数据的不一致。我们先写数据库落盘,再将删除缓存操作放入消息队列中,多次重试删除缓存来保证两个操作均被执行。这样在新的访问到达时会直接访问数据库,读取最新数据并重新缓存入Redis
操作顺序\数据值 | 数据库 | 缓存值 |
---|---|---|
1.写数据库成功 | x=old —> x=new | x=old |
2.加入消息队列 | x=new | x=old |
3.消费删缓存失败 | x=new | x=old |
4.消费删缓存成功 | x=new | delete x |
5.若多次失败则告警人工解决 | x=new | delete x |
6.新访问到达读取缓存 | x=new | 无缓存,重新load |
7.访问结束 | x=new | x=new |
为什么会并发不一致
由于这两个操作不满足原子性,导致写数据库成功后、删除缓存旧值之前,新来的读线程会读到缓存中的旧数据
线程A | 线程B | 线程C | 线程D |
---|---|---|---|
写数据库x=new | |||
读取缓存中的旧数据x=old | |||
删除缓存x=old | |||
读缓存未命中重新load,改读数据库x=new | |||
缓存命中x=new |
实现一致性解决方案
强一致性方案
使用锁保证数据库写和缓存写的原子性,可确保强一致性,但是在高并发场景中会牺牲性能,适用于数据严格要求一致性的场景
最终一致性
上述并发不一致的场景中,由于删缓存速度较快,仅会出现短暂不一致,后续进入的线程均读到最新数据,适合允许数据出现短暂不一致的场景