2010-12-04 58 views
7
基于设定的约束

我还是什么,必须与CQRS建筑风格基本(和解决)问题挣扎根源是什么?实现在CQRS

以一个预订应用程序为例。它可以让您预订音乐会的门票,电影席位或餐厅的桌子。在所有情况下,只会出售有限数量的“商品”

让我们假设事件或地点非常受欢迎。当新的事件或时间段的销售开放时,预订开始很快到达 - 可能每秒钟很多。

在查询方面,我们可以大规模地进行扩展,并且保留放在队列中由独立组件异步处理。首先,当我们从队列中取消预订命令时,我们将接受它们,但是在某个时候,我们将不得不开始拒绝其余的

我们如何知道何时达到极限?

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

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

一种选择是保持单独的聚合根,仅仅保持当前计数的轨道,就像这样:

  • AR:预定(谁多少?)
  • AR:事件/时隙/日期(汇总数)

第二聚合根将是第一个的非规范化的聚集,但是当underlyi ng数据存储不支持事务,那么它们很可能在高容量场景中出现不同步(这正是我们首先要解决的问题)。

一个可能的解决方案是序列化处理预留命令,以便一次只处理一个,但这违背了我们的可扩展性(和冗余)目标。

这样的场景让我想起了标准的“断货”的情况,但不同的是,我们不能很好地把保留在后的顺序。一旦一个事件售罄,它已经售罄,所以我看不出会有什么补偿行动。

我们如何处理这种情况?

回答

3

经过一段时间的思考之后,我终于明白,底层问题与CQRS的关系比与不同REST服务的非事务性质有关。

真的可以归结为这个问题:如果您需要更新多个资源,如果第二次写入操作失败,如何确保一致性?

让我们假设我们想要按顺序将更新写入资源A和资源B.

  1. 资源共享的成功更新
  2. 更新资源B失败

第一次写操作不能很容易地在一个异常的脸被回滚,所以我们可以做什么的尝试?捕获和抑制异常以对资源A执行补偿行为不是可行的选择。首先实施起来很复杂,但其次它并不安全:如果因网络连接失败而发生第一次异常,会发生什么情况?在这种情况下,我们无法针对资源A编写补偿措施。

关键在于明确的幂等性。虽然Windows Azure队列不能保证恰好一次语义,但他们确保至少一次语义。这意味着在面临间歇性例外情况时,该消息稍后将被重播

在前面的情况下,这是随后发生的事情:

  1. 资源则尝试更新。但是,重放被检测到,所以A的状态不受影响。但是,“写入”操作成功。
  2. 资源B已成功更新。

当所有的写操作是幂等的,最终一致性可以与消息来实现重放

2

有趣的问题和这个你是钉在CQRS的痛点之一。

方式亚马逊正在处理这是通过业务场景与错误状态应付,如果请求的项目已经卖完了。错误状态仅仅是通过电子邮件通知客户所请求的物品当前没有库存以及估计的装运日期。

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

考虑出售门票的场景我会确保告诉客户他们提出的要求是预订请求。预订请求会尽快得到处理,并且稍后他们会在邮件中重新获得最终答案。通过让这些客户可能会收到拒绝他们请求的电子邮件。

现在。我们能否让这个重新解决问题变得不那么痛苦?当然。通过在我们的分布式缓存中插入一个密钥,其中包含库存物品的百分比或数量,并在销售物品时减少此计数器。通过这种方式,我们可以在发出预订请求之前提醒用户,假设只剩下最初数量的10%,那么客户可能无法获得相关商品。如果柜台为零,我们会拒绝接受任何更多的预订请求。

我的观点之中:

1)让用户知道,那就是他们提出请求,而这可能会拒绝 2)通知成功的用于获取有关项目的机会是用户低

对于你的问题不完全是一个精确的答案,但这是我处理CQRS时处理这种情况的方式。

+0

我同意设定期望值也是一个重要的部分,但我把它看作一个给定的 - 这在技术上很容易处理。问题是:在没有锁定和交易的情况下,我们如何可靠且一致地减少大量并发情况下剩余可用座位/票的柜台? – 2010-12-04 22:58:42

+0

柜台只是作为告知客户的方式,而不是他们要求的最终答案。你总是需要检查数据库以确认。如果您有多个后台工作人员处理预留请求,则计数器确实需要一个锁(当您调用增量(键)或减量(键)时,memcached是原子的 - 因此在这里没有问题)。但是考虑到你的空间资源情况(比如音乐会的门票),柜台上锁定的几毫秒是可以接受的(请记住,这将快速缩小为零,并且新请求将被拒绝) – 2010-12-04 23:04:41

+0

如果我采用该路线(我可能),这个问题归结为:你如何*取消对Azure Tables的锁定?我不认为你可以... – 2010-12-06 09:29:31

1

eTag启用乐观并发性,您可以使用乐观并发来代替事务锁定来更新文档并安全地处理潜在的竞争条件。请参阅http://msdn.microsoft.com/en-us/library/dd179427.aspx备注以了解更多信息。

故事可能是这样的: 用户A创建一个事件E,最大票数为2,电子标签为123.由于高需求,3个用户几乎同时尝试购买票据。 用户B创建了一个预约请求B. 用户C创建一个预订请求C. 用户d创建了一个预约请求D.

系统S收到预约请求B,与ETAG 123读取事件和改变事件为具有1剩余票证,S提交包含与原始eTag匹配的eTag 123的更新,以便更新成功。电子标签现在是456.预订请求被批准,并且用户被通知成功。

另一个系统S2在系统S正在处理请求B的同时接收预留请求C,因此它还读取事件,带有电子标签123的事件将其更改为剩余一张票并尝试更新文档。但是,这次eTag 123不匹配,因此更新失败并出现异常。系统S2试图通过重新读取现在具有eTag 456并且计数为1的文档来重试该操作,使其递减到0并且用eTag 456重新提交。

对于用户C用户不幸的是,系统S开始处理用户D的请求在用户B之后立即并且还用eTag 456读取文档,但是因为系统S比系统S2更快,所以它能够在系统S2之前用eTag 456更新事件,所以用户D也成功地保留了他的票据。 eTag现在是789

因此,系统S2再次失败,给它一次尝试,但这次它使用eTag 789读取事件时,它看到没有票据可用并因此拒绝用户C的预留请求。

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

1

让我们看看企业的角度来看(我在处理类似的事情 - 在空闲插槽进行预约)...

在分析的第一件事情是给我的印象是关闭时,有没有一个预留的概念门票/座/表。这些是预订的资源。

如果是交易型,您可以使用某种形式的唯一性以确保不会对同一张票/座位/表发生重复预订(更多信息请致电http://seabites.wordpress.com/2010/11/11/consistent-indexes-constraints)。该场景需要同步(但仍然是并发)命令处理。

如果不是事务性的,可以追溯性地监视事件流并补偿命令。您甚至可以让最终用户体验等待预订确认的体验,直到系统知道确切的事件 - 即在事件流分析之后 - 该命令已完成并且是否得到补偿(归结为“是否预订?是或否?”)。换句话说,补偿可以成为确认周期的一部分。

让我们退一步更多...

当结算参与以及(如网上售票),我认为这整个场景演变成一个传奇反正(预留门票+车票账单)。即使没有结算,你也会有一个传奇(预定表+确认预订),以使体验可信。所以,即使你只是放在预订票/桌/座位的一个方面(即它仍然可用),“长期运行”的交易是不完整的,直到我付钱或直到我确认它。无论如何,补偿将会发生,当我以任何原因中止交易时,将再次解除罚单。现在有趣的部分就是企业如何处理这个问题:如果我们给他/她同一张票,也许其他一些客户会完成交易。在这种情况下,当重复预订门票/座位/桌子时,退款可能会变得更有趣 - 甚至为下一个/类似事件提供折扣以弥补不便。答案在于商业模式,而不是技术模型。