1. 什么场景下会出现数据一致性的问题?
结论:使用缓存的场景都有可能出现数据一致性问题。举几个简单的例子:
- 早期使用本地 Word 文档时,如果忘记保存辛苦写下的几千字内容,系统崩溃或异常退出就可能导致数据丢失。
- 在玩单机游戏时,如果在通关进度中途机器异常重启,所有未保存的进度都需要重新进行。
这些例子都体现了缓存数据和持久存储数据之间的一致性问题。
2. 什么原因导致了数据一致性问题?
假设你使用缓存来加速访问某些热点数据,缓存的更新策略是“先读缓存,如果缓存没有数据,再读数据库,并将数据更新到缓存层”。在这种场景下,可能导致数据不一致的因素有以下几个:
-
并发操作:两个请求 A 和 B 同时到来。
- A 是读请求,发现缓存没有数据,于是去数据库读取,并将数据更新到缓存中。
- B 是修改请求,在 A 从数据库读取数据后,对该数据进行了修改。此时,缓存中的数据将与数据库中的数据不一致。
-
异常情况:在上面的例子中,如果我们在 B 修改数据后再更新缓存,这样行不行?理想情况下,这样做是可行的,但在现实中必须考虑各种异常情况。例如:
- 数据库修改成功,但更新缓存时操作失败。
- 网络延迟过高导致缓存更新超时。
- 服务器异常导致缓存更新未能完成。
3. 业内有哪些成熟的缓存策略来解决数据一致性问题?
思考一下,是否可以解决上述两个原因?并发问题是可以解决的,但异常情况是不可控的。因此,解决问题的思路是在考虑异常情况下解决并发问题。
-
强一致性:
- 使用写穿透(Write-Through)策略和分布式锁。在更新数据时,使用分布式锁阻塞其他读写操作,先删除缓存,再更新数据库。在这种情况下,没有并发问题,只需考虑操作失败的情况。删除缓存后,即使数据库更新失败,也不会影响数据的一致性。不过,这种策略的性能是最差的。
-
最终一致性:
-
延时双删策略:在更新数据库后,立即删除缓存一次,然后等待一段时间后再删除一次缓存。这个策略的关键在于两次删除缓存。当第一次删除缓存的瞬间,如果有一个读请求和一个写请求同时到来,会发生什么情况?读请求会穿透到数据库层(期间写请求更新数据库,然后再次删除缓存),并更新缓存层。
为什么需要第二次删除缓存?因为网络的不确定性可能导致缓存更新缓慢,例如网络延迟或 SQL 执行慢等问题。第二次删除是为了消除大部分异常因素的影响。然而,这种策略在极端情况下仍然无法保证一致性,因此需要一个定期检查缓存和数据库一致性的守护进程。
-
写回(Write-Back)模式:在这种模式下,修改操作只写入缓存,数据库更新延后(通过守护进程或后台任务同步)。由于操作仅针对一个数据源,另一个数据源通过单线程同步,最终一致性得以保证,有点类似于 MySQL 的主从复制。但这种模式存在数据丢失的风险。
-
4. 我该选择哪种缓存策略?
结合业务需求选择适合的缓存策略:
- 如果要求强一致性,选择 写穿透(Write-Through) 策略。
- 如果要求高性能和最终一致性,选择 写回(Write-Back) 模式。
5. 总结
没有最好的缓存策略,只有最适合的缓存策略。根据具体的业务需求和系统特点,选择合适的策略来权衡一致性与性能。