简体   繁体   English

使用MemoryCache的数据存储库

[英]Data Repository using MemoryCache

I built a homebrew data entity repository with a factory that defines retention policy by type (eg absolute or sliding expiration). 我建立了一个带有工厂的自制数据实体存储库,该存储库按类型(例如,绝对或滑动到期)定义了保留策略。 The policy also specifies the cache type as httpcontext request, session, or application. 该策略还将高速缓存类型指定为httpcontext请求,会话或应用程序。 A MemoryCache is maintained by a caching proxy in all 3 cache types. MemoryCache由所有三种缓存类型中的缓存代理维护。 Anyhow, I have a data entity service tied to the repository which does the load and save for our primary data entity. 无论如何,我有一个与存储库绑定的数据实体服务,该存储库为我们的主要数据实体进行加载和保存。 The idea is you use the entity repository and don't need to care if the entity is cached or retrieved from it's data source (db in this case). 这个想法是您使用实体存储库,不需要关心是否从数据源(在本例中为db)缓存或检索了实体。

An obvious assumption would be that you would need to synchronise the load/save events as you would need to save the cached entity before loading the entity from it's data source. 一个显而易见的假设是,您需要同步加载/保存事件,就像在从数据源加载实体之前要保存该缓存的实体一样。

So I was investigating a data integrity issue in production today... :) 所以我今天正在调查生产中的数据完整性问题... :)

Today I read there can be a good long gap between the entity being removed from the MemoryCache and the CacheItemRemovedCallback event firing (default 20 seconds). 今天,我读到从MemoryCache中删除的实体与CacheItemRemovedCallback事件触发(默认20秒)之间可能存在很长的差距。 The simple lock I had around the load and save data ops was insufficient. 我在加载和保存数据操作方面的简单锁定是不够的。 Furthermore the CacheItemRemovedCallback was in it's own context outside of HttpContext making things interesting. 此外,CacheItemRemovedCallback位于HttpContext之外的它自己的上下文中,这使事情变得有趣。 It meant I needed to make the callback function static as I was potentially assigning a disposed instance to the event. 这意味着我需要使回调函数静态化,因为我可能会为事件分配一个已处置的实例。

So once I realised there was was the possibility of a gap whereby my data entity no longer existed in cache but might not have been saved to it's data source might explain the 3 corrupt orders out of 5000. While filling out a long form it would be easy to perform work beyond the policy's 20 minute sliding expiration on the primary data entity. 因此,一旦我意识到存在一种可能性,即我的数据实体不再存在于高速缓存中,但可能尚未保存到其数据源中,那么它可能会解释5000个错误订单中的3个。在策略在主数据实体上滑行20分钟后,可以轻松执行工作。 That means if they happen to submit at the same moment of expiration an interesting race condition between the load (via request context) and save (via cache expired callback) emerges. 这意味着,如果它们恰好在到期的同一时间提交,则会在负载(通过请求上下文)和保存(通过缓存过期的回调)之间出现一个有趣的竞争条件。

With a simple lock it was the roll of the dice, would save or load win? 用一个简单的锁就可以掷骰子,是省还是赢? Clearly we need a save before the next load from the data source (db). 显然,在下一次从数据源(db)加载之前,我们需要保存。 Ideally when an item expires from the cache it is atomically written to it's data source. 理想情况下,当项目从缓存中过期时,会将其原子写入其数据源。 with the entity gone from the cache but the expired callback not yet fired a load operation can slip in. In this case the entity will not be found in the cache so will default to load from the data source. 如果实体已从缓存中移出,但尚未触发的过期回调可能会进入加载操作。在这种情况下,将不会在缓存中找到该实体,因此默认情况下将从数据源加载。 However, as the save operation may not have commenced resulting in data integrity corruption and will likely clobber your now saved cached data. 但是,由于可能尚未开始进行保存操作,从而导致数据完整性损坏,并且可能会破坏您现在保存的缓存数据。

To accomplish synchronisation I need a named signalling lock so I settled on EventWaitHandle. 为了完成同步,我需要一个命名的信号锁,因此我选择了EventWaitHandle。 A named lock is created per user which is < 5000. This allows the Load to wait on a signal from the expired event which Saves the entity (whose thread exists in its own context outside HttpContext). 将为每个用户创建一个小于5000的命名锁。这将允许Load等待来自过期事件的信号,该事件将保存该实体(该线程的内存在HttpContext之外的自身上下文中)。 So in the save it is easy to grab the existing name handle and signal the Load to continue once the Save is complete. 因此,在保存中,很容易抓住现有的名称句柄,并在完成保存后发出信号通知加载继续。

I also have a redundancy where it times out and logs each 10 seconds block by the save operation. 我也有一个冗余,它超时并通过保存操作记录每个10秒的块。 As I said, the default is meant to be 20 seconds between an entity being removed form MemoryCache and it being conscious of it to fire the event which in turn saves the entity. 就像我说的那样,默认值是从MemoryCache删除实体到意识到触发事件从而保存该实体之间的20秒。

Thank you to anyone who followed my ramblings through all that. 谢谢所有跟随我漫无目的的人。 Given the nature of the sync requirements was the EventWaitHandle lock the best solution? 考虑到同步要求的性质,EventWaitHandle锁是最好的解决方案吗?

For completeness I wanted to post what I did to address the issue. 为了完整起见,我想发布我为解决该问题所做的工作。 I made multiple changes to the design to create a tidier solution which did not require a named sync object and allowed me to use a simple lock instead. 我对设计进行了多项更改,以创建一个整理器解决方案,该解决方案不需要命名的同步对象,并允许我使用简单的锁。

First the data entity repository is a singleton which was stored in the request cache. 首先,数据实体存储库是一个单例,存储在请求缓存中。 This front end of the repository is detached from the cache's themselves. 存储库的前端与缓存本身分离。 I changed it to reside in the session cache instead which becomes important below. 我将其更改为驻留在会话缓存中,这在下面变得很重要。

Second I changed the event for the expired entity to route through the data entity repository above. 其次,我更改了过期实体的事件以通过上面的数据实体存储库进行路由。

Third I changed the MemoryCache event from RemovedCallback to UpdateCallback**. 第三,我将MemoryCache事件从RemovedCallback更改为UpdateCallback **。

Last, we tie it all together with a regular lock in the data entity repository which is is the user's session and the gap-less expiry event routing through the same allowing the lock to cover load and save (expire) operations. 最后,我们将其与数据实体存储库中的常规锁(这是用户的会话)和通过它们的无间隙到期事件路由绑定在一起,以允许该锁覆盖加载和保存(到期)操作。


** These events are funny in that A) you can't subscribe to both and B) UpdateCallback is called before the item is removed from the cache but it is not called when you explicitly remove the item (aka myCache.Remove(entity) won't call event but UpdateCallback will). **这些事件很有趣,因为A)您不能同时订阅这两个事件,并且B)在从缓存中删除该项目之前调用UpdateCallback,但是在您明确删除该项目(即myCache.Remove(entity))时不会调用它。不会调用事件,但会调用UpdateCallback)。 We made the decision if the item was being forcefully removed from the cache that we didn't care. 我们决定是否将该项目从我们不在乎的缓存中强行删除。 This happens when the user changes company or clears their shopping list. 当用户更换公司或清除其购物清单时,就会发生这种情况。 So these scenarios won't fire the event so the entity may never be saved to the DB's cache tables. 因此,这些情况不会触发该事件,因此该实体可能永远不会保存到DB的缓存表中。 While it might have been nice for debugging purposes it wasn't worth dealing with the limbo state of an entity's existence to use the RemovedCallback which had 100% coverage. 尽管可能出于调试目的很好,但使用具有100%覆盖率的RemovedCallback处理实体存在的边缘状态并不值得。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM