考虑以下两种用伪代码编写的方法,它们分别获取复杂的数据结构并对其进行更新:
getData(id) {
if(isInCache(id)) return getFromCache(id) // already in cache?
data = fetchComplexDataStructureFromDatabase(id) // time consuming!
setCache(id, data) // update cache
return data
}
updateData(id, data) {
storeDataStructureInDatabase(id, data)
clearCache(id)
}
在上面的实现中,并发存在问题,我们可能最终得到缓存中过时的数据:考虑分别运行getData()
和updateData()
的两个并行执行。如果第一次执行从另一个执行调用storeDataStructureInDatabase()
和clearCache()
之间准确地从缓存中获取数据,那么我们将获得过时的数据版本。你会如何解决这个并发问题?
我考虑了以下解决方案,其中缓存在提交数据之前无效:
storeDataStructureInDatabase(id, data) {
executeSql("UPDATE table1 SET...")
executeSql("UPDATE table2 SET...")
executeSql("UPDATE table3 SET...")
clearCache(id)
executeSql("COMMIT")
}
但话又说回来:如果一个执行在另一个执行调用clearCache()
和COMMIT
之间读取缓存,那么过时的数据将被提取到缓存中。问题没有解决。
答案 0 :(得分:2)
在缓存思维方式中,您无法阻止检索过时的数据。
例如,当有人开始发送HTTP请求(如果您的应用程序是Web应用程序),稍后将缓存无效时,我们是否应该在POST请求启动时认为缓存无效?当您的服务器处理请求时?当你启动控制器代码?那么没有。实际上只有在数据库事务结束时缓存才有效。甚至在事务开始时,仅在结束时,在事务的COMMIT阶段。使用以前数据的任何工作流程很少有机会意识到数据已更改,在Web应用程序中,显示浏览器中显示过时数据的html页面,您是否要刷新这些页面?
但是,让我们认为您的并行流程不仅适用于Web,而且适用于真正的并发关键并行作业。
一个问题是您的缓存不是由数据库服务器处理的,因此它不在事务COMMIT / ROLLBACK中。您无法首先清除缓存,但如果您回滚则重建缓存。因此,您只能在提交事务后清除并重建缓存。
如果你的get介于数据库提交和缓存清除指令之间,那么就有可能获得一个过时的缓存版本。所以:
您可能会接受使用过时的缓存数据。唯一非常重要的一点是如果您不能信任您的缓存数据以获得真正关键的东西,那么您就不应该使用缓存。例如,如果您正在操作会计数据。获得并行任务序列化的唯一方法是: