【Redis】Redis缓存与数据库DB数据如何保持同步?
需要让数据库与redis高度保持一致,因为要求时效性比较高。采用的读写锁保证的强一致性。使用Redisson实现读写锁。在读的时候添加共享锁,可以保证读读不互斥、读写互斥。当我们更新数据的时候,添加排他锁。它是读写、读读都互斥,这样就能保证在写数据的同时,是不会让其他线程读数据的,避免了脏数据。这里面需要注意的是,读方法和写方法上需要使用同一把锁才行。
场景:图书馆的公共记事本
假设有一个公共图书馆,规则如下:
- MySQL数据库 = 图书馆中央书库里唯一的一本master 记事本(唯一正本)。所有重要的记录都记在这里,它绝对正确,但放在库里,查阅起来很慢。
- Redis缓存 = 图书馆大厅公告板。管理员会把master记事本上的热门内容抄录一份贴在这里,大家查阅起来非常快。
- 用户 = 来图书馆的读者。
现在问题来了:如何保证公告板(Redis) 上的内容,永远和中央书库里的master记事本(MySQL) 内容一致?
如果没有锁( naive 双写):混乱的局面
- 读者A想要读一条数据。他走到公告板前,开始阅读。
- 同时,读者B想要更新这条数据。他做了两件事:
- 他跑去中央书库,修改了master记事本。
- 然后他走到公告板前,准备把公告板上的旧内容擦掉,改成新的。
- 问题发生:就在读者B擦掉了旧内容,但还没写完新内容的那个瞬间!
- 读者A正好读完了公告板上旧内容的前半句,抬头一看,发现内容被擦掉了一半(读到了脏数据),或者读到了正在写的一半新一半旧的内容,完全看不懂了。
这就是脏读。在没有保护的情况下,读写同时进行就会导致这种问题。
解决方案:Redisson读写锁 (ReadWriteLock)
图书管理员引入了两把特殊的锁和一套规则:
- 共享锁 (Read Lock - 读锁):一把绿色的锁,有很多把一模一样的副本。
- 排他锁 (Write Lock - 写锁):一把红色的锁,全世界只有一把。
规则一:读操作(上共享锁)
当有读者(比如读者A)想去公告板读数据时,他必须遵循:
- 他先去锁架子上拿一把绿色的共享锁,挂在公告板上。
- 如果公告板上已经挂了一把绿色锁:说明还有其他人在读。没关系! 绿色锁允许多人一起读(共享)。读者A可以直接和其他人一起阅读。
- 他读完后,会把自己拿的那把绿色锁从公告板上取下放回原处。
规则二:写操作(上排他锁)
当有读者(比如读者B)想更新数据时,他必须遵循:
- 他需要做两件事:a. 去中央书库改master记事本。 b. 更新公告板。
- 在开始做任何事之前,他必须先去锁架子上拿那把唯一的红色排他锁,挂在公告板上。
- 红色锁的霸道规则:
- 如果公告板上已经有绿色锁(有人在读):对不起,读者B必须等着!直到所有读者读完,把他们的绿色锁都拿走为止。红色锁不允许任何其他锁存在。
- 如果公告板上是空的:读者B成功挂上红色锁。
- 如果公告板上已经有红色锁:说明其他人在写,读者B也必须等着。
- 读者B挂上红色锁后,就意味着:“现在我要开始写了,所有人都给我闪开!”
- 这时,任何想来读数据的人(想拿绿色锁)都会被拦住,他们必须等读者B写完。
- 读者B完成所有操作(更新书库master本 + 更新公告板)后,把红色锁取下放回原处。
这个过程如何保证强一致性?
我们回到最初的混乱场景,现在看看会怎样:
- 读者A想读数据,他成功在公告板上挂了一把绿色共享锁,开始阅读。
- 这时,读者B想写数据。他试图去拿红色排他锁。
- 管理员拦住了他:“对不起,公告板上现在有绿色锁,你不能写,请排队等待。”
- 读者A慢悠悠地读完了,然后取下了自己的绿色锁。
- 此时公告板空了,管理员告诉读者B:“好了,现在你可以去挂红色锁了。”
- 读者B挂上红色锁,这意味着:所有后续的读者(读请求)都被挡住了公告板外。
- 读者B安全地先更新中央书库,再更新公告板。因为期间绝对没有其他人能来读公告板,所以不会有人读到“擦到一半”的脏数据。
- 读者B写完,取下红色锁。
- 等待的读者们一拥而上,他们读到的是读者B刚刚写好的最新、最完整、一致的数据。
总结一下你那段话的含义:
- 读写锁:就是那把绿色共享锁和红色排他锁。
- 读读不互斥:多个人可以同时拿绿色锁一起读(共享),效率高。
- 读写、写写互斥:只要有人拿了红色锁,其他所有人(无论是想读的还是想写的)都必须等待。这保证了写的原子性。
- 同一把锁:读和写必须操作同一个公告板(同一个key) 的锁才有效。你不能给“用户名”的缓存加锁,却去改“用户余额”的数据。
- 强一致性:通过“写操作阻塞所有读操作”这种稍微牺牲一点性能的方式,换来了数据的绝对准确。
这种方案非常适合对数据准确性要求极高的场景(如商品库存、账户余额),虽然性能不是最高的,但数据是最可靠的。