0

我构建了一个自制数据实体存储库,其中包含一个按类型定义保留策略(例如绝对或滑动到期)的工厂。该策略还将缓存类型指定为 httpcontext 请求、会话或应用程序。MemoryCache 由所有 3 种缓存类型中的缓存代理维护。无论如何,我有一个与存储库相关的数据实体服务,它为我们的主要数据实体加载和保存。这个想法是您使用实体存储库,并且不需要关心实体是否被缓存或从它的数据源(在本例中为 db)检索。

一个明显的假设是您需要同步加载/保存事件,因为您需要在从数据源加载实体之前保存缓存的实体。

所以我今天正在调查生产中的数据完整性问题...... :)

今天我读到从 MemoryCache 中删除的实体和 CacheItemRemovedCallback 事件触发之间可能存在很长的差距(默认为 20 秒)。我对加载和保存数据操作的简单锁定是不够的。此外,CacheItemRemovedCallback 位于 HttpContext 之外的自己的上下文中,这让事情变得有趣。这意味着我需要将回调函数设为静态,因为我可能会将已处置的实例分配给事件。

因此,一旦我意识到可能存在差距,即我的数据实体不再存在于缓存中,但可能没有保存到它的数据源中,这可能会解释 5000 个中的 3 个损坏订单。在填写长表格时,它将是易于在主数据实体上执行超出策略 20 分钟滑动到期的工作。这意味着如果他们碰巧在到期的同一时刻提交了加载(通过请求上下文)和保存(通过缓存过期回调)之间的有趣竞争条件。

用一个简单的锁它就是掷骰子,保存或加载会赢吗?显然,我们需要在下一次从数据源 (db) 加载之前进行保存。理想情况下,当一个项目从缓存中过期时,它会自动写入它的数据源。当实体从缓存中消失但过期回调尚未触发时,加载操作可能会进入。在这种情况下,将无法在缓存中找到实体,因此默认从数据源加载。但是,由于保存操作可能尚未开始,从而导致数据完整性损坏,并且可能会破坏您现在保存的缓存数据。

为了完成同步,我需要一个命名的信号锁,所以我选择了 EventWaitHandle。每个用户创建一个 < 5000 的命名锁。这允许 Load 等待来自保存实体的过期事件的信号(其线程存在于 HttpContext 之外的自己的上下文中)。所以在保存中很容易抓住现有的名称句柄并在保存完成后向加载发出信号以继续。

我还有一个冗余,它超时并通过保存操作记录每 10 秒块。正如我所说,默认值意味着从 MemoryCache 中删除一个实体到它意识到它会触发该事件进而保存实体之间的 20 秒。

感谢所有跟随我的漫谈的人。鉴于同步要求的性质,EventWaitHandle 锁是最佳解决方案吗?

4

1 回答 1

0

为了完整起见,我想发布我为解决这个问题所做的事情。我对设计进行了多项更改,以创建一个更整洁的解决方案,它不需要命名同步对象,并允许我使用简单的锁。

首先,数据实体存储库是存储在请求缓存中的单例。存储库的这个前端与缓存本身是分离的。我将其更改为驻留在会话缓存中,这在下面变得很重要。

其次,我将过期实体的事件更改为通过上面的数据实体存储库进行路由。

第三,我将 MemoryCache 事件从 RemovedCallback 更改为 UpdateCallback**。

最后,我们将它们与数据实体存储库中的常规锁联系在一起,这是用户的会话和无间隙的到期事件路由,允许锁覆盖加载和保存(到期)操作。


** 这些事件很有趣,因为 A)您不能同时订阅和 B)在从缓存中删除项目之前调用 UpdateCallback,但在显式删除项目时不会调用它(又名 myCache.Remove(entity)不会调用事件,但 UpdateCallback 会)。我们决定是否将该项目从我们不关心的缓存中强制删除。当用户更换公司或清除他们的购物清单时,就会发生这种情况。所以这些场景不会触发事件,因此实体可能永远不会保存到数据库的缓存表中。尽管出于调试目的可能很好,但不值得使用具有 100% 覆盖率的 RemovedCallback 来处理实体存在的不确定状态。

于 2017-03-13T15:53:29.540 回答