我仍在为与CQRS样式架构相关的基本(和解决)问题而苦苦挣扎:

我们如何实施依赖的业务规则 一套 总根?

以预订申请为例。它可能使您能够预订演唱会的门票,拍电影的座位或餐厅的桌子。在所有情况下,只有 有限的数字 出售的“物品”。

让我们想象事件或地点非常受欢迎。当新活动或时间段的销售开放时,预订开始很快就开始到达 - 也许每秒很多。

在查询方面,我们可以大规模扩展,并将预订放在队列上以通过自主组件异步处理。首先,当我们从队列中删除预订命令时,我们将接受它们,但是在某个时候,我们将不得不开始 拒绝其余的.

我们怎么知道何时达到极限?

对于每个预订命令,我们必须查询某种商店才能确定是否可以容纳请求。这意味着我们需要知道当时已经收到了多少保留。

但是,如果域存储是一个非关系数据存储,例如Windows Azure表存储,我们不能很好地做一个 SELECT COUNT(*) FROM ...

一种选择是保留 单独的聚集根 这只是跟踪当前的数量,这样的人:

  • AR:预订(谁?多少?)
  • AR:事件/时间插槽/日期(总数)

第二个汇总根部将是第一个统计的聚合,但是当基础数据存储不支持交易时,很可能在高量的场景中可以摆脱同步(这就是我们试图尝试的东西)地址首先)。

一种可能的解决方案是 连载 处理保留命令,因此一次只能处理一个,但这违背了我们的可扩展性目标(和冗余)。

这种情况使我想起了标准的“库存”方案,但区别在于我们不能很好地将预订放在后订单上。事件售罄后,它就卖光了,所以我看不出会有什么补偿行动。

我们如何处理这种情况?

有帮助吗?

解决方案

在考虑了一段时间之后,我终于意识到,基本问题与CQR的关系少于与与CQR的关系 非横断性 不同的休息服务的性质。

确实可以归结为这个问题:如果您需要更新几个资源,那么如果第二个写操作失败,如何确保一致性?

让我们想象,我们想按顺序为资源A和资源B编写更新。

  1. 资源A已成功更新
  2. 尝试更新资源B的尝试失败

面对例外情况,第一个写操作不能轻易回滚,那么我们该怎么办?捕捉和抑制异常以对资源A执行补偿措施并不是可行的选择。首先,实施很复杂,但其次是不安全的:如果由于网络连接失败而发生第一个例外情况会发生什么?在这种情况下,我们也不能对资源编写赔偿措施。

关键在于显式 能力. 。虽然Windows Azure队列不保证 完全是一次 语义,他们确实保证 至少一次 语义。这意味着面对间歇性例外,该消息稍后将是 重播.

在以前的情况下,这就是发生的事情:

  1. 资源A已尝试更新。但是,检测到重播,因此A的状态不受影响。但是,“写”操作成功了。
  2. 资源B已成功更新。

当所有写操作都是愿意的时, 最终的一致性 可以通过 消息重播.

其他提示

有趣的问题,有了这个问题,您正在确定CQR中的一个痛点之一。

亚马逊处理此操作的方式是,如果所请求的项目售罄,则使业务方案应对错误状态。错误状态仅仅是为了通过电子邮件通知客户,这些物品目前尚未库存和估计的运输日。

但是 - 这并不能完全回答您的问题。

考虑出出售门票的情况,我要确保告诉客户他们给出的请求是 预订请求. 。预订请求将尽快处理,并以后在邮件中恢复最终答案。通过这件事,一些客户可能会收到一封电子邮件,以拒绝他们的请求。

现在。我们可以使这种回旋减轻疼痛吗?当然。通过在分布式缓存中插入一个钥匙,并以库存中的物品百分比或数量插入,并在出售商品时减少此计数器。这样,我们可以在给出预订请求之前警告用户,假设如果仅保留初始数量的10%,则客户可能无法获得所涉及的项目。如果计数器为零,我们只会拒绝接受更多的预订请求。

我的观点是:

1)让用户知道这是他们正在提出的请求,这可能会被拒绝2)通知用户获得有关该项目的成功机会很低

并不是对您的问题的确切答案,但这是我在处理CQR时如何处理这样的方案。

ETAG启用了乐观的并发,您可以用来代替交易锁定来更新文档并安全地处理潜在的比赛条件。请参阅这里的言论 http://msdn.microsoft.com/en-us/library/dd179427.aspx 有关更多信息。

故事可能会像这样:用户A创建一个事件E,最高门票为2,ETAG为123。由于需求量很高,3用户几乎同时尝试购买门票。用户B创建预订请求B.用户C创建预订请求C.用户D创建了预订请求D.

System S接收预订请求B,使用ETAG 123进行读取事件,然后将事件更改为剩余的票,S提交了更新,其中包括与原始ETAG匹配的ETAG 123,因此更新成功。 ETAG现在为456。批准了预订请求,并通知用户成功。

另一个系统S2与系统S正在处理请求B的同时接收预订请求C,因此它还读取事件的ETAG 123事件123将其更改为剩余的票证,并尝试更新文档。但是,这次ETAG 123与不匹配,因此更新失败了异常。 System S2试图通过重新阅读现在具有ETAG 456的文档,计数为1来重试操作,从而将其降低到0并与ETAG 456重新提交。

不幸的是,对于用户C用户,系统S开始处理用户B后立即处理用户D请求,并使用ETAG 456读取文档,但是由于系统S比System S2快,因此能够在System S2之前使用ETAG 456更新事件成功保留了他的票。 ETAG现在是789

因此,System S2再次失败,再尝试一次,但是这次,当它使用ETAG 789读取事件时,它发现没有门票可用,因此拒绝了用户C的预订请求。

您如何通知用户他们的预订请求是否成功取决于您。您可以每隔几秒钟进行一次对服务器进行轮询,并等待更新预订状态。

让我们看一下业务的观点(我处理类似的事情 - 在免费老虎机上预订约会)...

您分析的第一件事使我感到不安,这是没有可保留的票/座位/桌子的想法。这些是正在预订的资源。

如果是交易的,则可以使用某种形式的唯一性来确保在同一票/座位/桌子上不会进行双重预订(更多信息 http://seabites.wordpress.com/2010/11/11/consistent-indexes-constraints)。这种情况需要同步(但仍然并发)命令处理。

如果不进行交易,您可以追溯监视事件流并补偿命令。您甚至可以为最终用户提供等待预订确认的体验,直到系统确定 - 即事件流进行分析后 - 命令完成并且已得到补偿或没有得到补偿(归结为“预订?是还是不是?”)。换句话说,补偿可以成为确认周期的一部分。

让我们退后一步...

当还涉及计费时(例如在线票证出售),我认为整个场景无论如何都会演变成传奇(预备票 +帐单票)。即使没有计费,您也会有一个传奇(预备表 +确认保留),以使体验可信。因此,即使您只放大了预订票/桌子/座位的一个方面(即仍然可用),“长期运行”的交易直到我付款或直到我确认它之前才完整。无论如何,赔偿都会发生,当我出于任何原因中流产交易时,再次释放了票。现在有趣的部分变成了企业想要处理的方式:如果我们给了他/她相同的票,也许其他一些客户将完成交易。在这种情况下,当双重预订机票/座位/桌子时,退款可能会变得更加有趣 - 甚至在下一个/类似活动上提供折扣,以弥补不便。答案在于业务模型,而不是技术模型。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top