2011-06-15 61 views
2

分别考虑以下两种方法,写的伪代码,即取一个复杂的数据结构,并进行更新,:如何使用内存高速缓存的并发性关键的上下文

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,然后过时的数据将被提取到缓存中。问题没有解决。

回答

2

在缓存的思维方式中,您无法阻止检索过时的数据。

例如,如果有人开始发送HTTP请求(如果您的应用程序是Web应用程序),稍后会导致缓存无效,那么POST请求启动时应该认为缓存无效吗?当请求被你的服务器处理时?当你启动控制器代码?那么。实际上,仅当数据库事务结束时,缓存无效。即使事务在交易的COMMIT阶段开始,也只是在最后。任何使用以前数据的工作流程都很少有机会意识到数据在Web应用程序中发生了变化,浏览器中显示过时数据的HTML页面是否需要刷新这些页面?

但让我们认为你的并行进程不仅仅是为了网络,而是为了真正的并行关键并行作业。

一个问题是您的缓存不是由数据库服务器处理的,所以它不在事务COMMIT/ROLLBACK中。您不能决定首先清除缓存,但如果您回滚则重建它。因此,您只能在交易完成后清除并重建缓存

而且,如果您的get在数据库提交和缓存清除指令之间,那么就有可能获得过期版本的缓存。所以:

  • 是否真的很重要,你有一个过时版本的缓存?假设你的并行进程在检索这个新版本(这是旧的版本)之前做了几毫秒的事情,并且使用它可能需要40ms,然后在不留意高速缓存已经在15ms之前刷新的情况下生成最终报告工作的结束。如果您的流程响应不能包含任何过时的数据,那么在输出它之前,您必须检查数据的有效性(因此您应该重新检查工作流程中使用的所有数据是否仍然有效)。
  • 所以,如果你不想重新检查是否意味着开始,只能在工作结束解除锁定你的进程应该把一些锁(信号?)数据的有效性,你是序列化您工作。数据库可以通过伪系列化水平工作事务和破坏你的交易如果有变化使得这种伪系列化hasardous加快系列化。但是,在这里你不仅要处理数据库,所以你应该在你自己的一方做序列化。
  • 过程系列化是缓慢的,但你可以尝试做相同的数据库,这是乳宁在并行作业和无效数据时,改变运行的任何工作(所以有一些检测缓存明确,杀死并重新运行所有现有并行作业,这意味着你有什么掌握所有的并行作业)
  • 简单地接受,你可以有过去的,无效的,过时的小数据。如果我们谈论网络应用程序,则您的响应在TCP/IP上传递给客户端浏览器的时间可能已经失效。

很可能您会接受使用过期的缓存数据。唯一真正重要的是如果你不能相信你的缓存数据是一件非常关键的事情,那么你不应该为那个使用缓存。例如,如果您正在操作会计数据。得到的并行任务序列化的唯一途径就是要做到:

    在写作过程中
  • :所有重要读取(一个将得到一些写)和所有写东西交易具有高隔离级别(级别4)并与所有必要行锁。这是一件很难做到的事情,只能用数据库工作,如果你为读取操作添加一个外部缓存是完全不可能的。
  • 并行读取的过程:你想要做什么(从外部读取缓存),如果读取数据将不会被用于写操作。如果其中一个读取的数据稍后将用于写入操作,则必须在写入事务中检查该数据有效性(因此在写入过程中)。 为什么不在数据上添加时间戳水印,以便它在写回操作时能够知道它是否仍然有效。
+0

一个令人印象深刻和复杂的答案!谢谢! – someName 2011-06-15 14:01:29