【问题标题】:How do you ensure consistent client reads in an eventual consistent system?在最终一致的系统中,您如何确保一致的客户端读取?
【发布时间】:2012-04-30 07:08:15
【问题描述】:

我正在研究 CQRS,我正在寻找有关如何在最终一致的系统中解决客户端读取问题的文章。例如,考虑一个网上商店,用户可以在其中将商品添加到他们的购物车中。如果“AddItemToCart”命令的实际处理是异步完成的,如何确保客户端显示购物车中的商品?我了解基于域事件异步调度命令和更新读取模型异步的原理,但我看不到从客户端的角度来看这是如何处理的。

【问题讨论】:

  • 根据 Udi Dahan 的说法,购物车是一个 classic example 的域,CQRS 并不适合。
  • 亚马逊呢?他们做最终的一致性。无论如何,它不是重点。我不是在创建一个最终一致的系统,我只是在问如何做到这一点。

标签: cqrs eventual-consistency


【解决方案1】:

有几种不同的方法;

等待用户直到一致

只需轮询服务器,直到您更新读取模型。这与 Ben 所展示的相似。

通过 2PC 确保一致性

您有一个支持 DTC 的队列;并且您的命令首先放在那里。然后他们是;执行、发送事件、更新读取模型;都在一个事务中。但是,您实际上并没有通过这种方法获得任何收益,因此请不要这样做。

欺骗客户

将读取的模型放在客户端的本地存储中,并在发送相应事件时更新它们——但无论如何您都期待这个事件,所以您已经更新了购物车的 javascript 视图。

【讨论】:

  • 我不明白你所说的选项 2 是什么意思。NServiceBus 中的 DTC 只是确保输入/输出队列与处理消息时完成的任何数据库操作之间的一致性。端点之间没有单一事务。
  • NServiceBus - 这有什么关系?这就是 NServiceBus 的工作原理,这对我来说很好。
  • 嗯,也许你的意思是;我将如何创建跨越队列和发送的一致性边界?您必须在发送方和接收方之间共享事务句柄。
  • 不,我的意思是我在问题中写的内容。读模型应该被认为是一个自治组件,任何更新读模型的失败都不应该导致写模型的回滚。无论如何,我同意选项 2(如果有人使用可以做类似事情的技术)听起来是个坏主意。
  • 那么我猜你可以选择 1 和 3。
【解决方案2】:

我建议您查看Microsoft Patterns & Practices team's guidance on CQRS。尽管这仍在进行中,但他们已经为您提出的问题提供了一种解决方案。

对于需要反馈的命令,他们的方法是异步提交命令,重定向到另一个控制器操作,然后轮询读取模型以了解预期的更改或发生超时。这是使用 Post-Redirect-Get 模式,该模式与浏览器的前进和后退导航按钮配合得更好,并在 MVC 控制器开始轮询之前为基础架构提供更多时间来处理命令。

RegistrationController 中使用 ASP.NET MVC 4 异步控制器的示例代码。

[HttpGet]
[OutputCache(Duration = 0, NoStore = true)]
public Task<ActionResult> SpecifyRegistrantAndPaymentDetails(Guid orderId, int orderVersion)
{
    return this.WaitUntilOrderIsPriced(orderId, orderVersion)
        .ContinueWith<ActionResult>(

        ...

    );
}

...

private Task<PricedOrder> WaitUntilOrderIsPriced(Guid orderId, int lastOrderVersion)
{
    return
        TimerTaskFactory.StartNew<PricedOrder>(
            () => this.orderDao.FindPricedOrder(orderId),
            order => order != null && order.OrderVersion > lastOrderVersion,
            PricedOrderPollPeriodInMilliseconds,
            DateTime.Now.AddSeconds(PricedOrderWaitTimeoutInSeconds));
}

我可能会使用 AJAX 轮询,而不是在服务器上阻止 Web 请求。

【讨论】:

  • 顺便说一句; DateTime.Now 遇到所有类型的问题,应该避免(并且它是本地化的并且有闰秒等)。 Thread.Sleep(500) 也是服务请求的 Web 服务器的一大禁忌;最好使用任务驱动的 UI。
  • @Henrik 我刚刚使用 ASP.NET MVC 4 异步控制器使用 CQRS 指南中的最新示例代码更新了我的答案。没有更多的 Thread.Sleep!
【解决方案3】:

重定向后获取

您希望在调用 Get 之前按时执行保存命令。如果命令在后端需要 10 秒才能完成,但 Get 在 1 秒内被调用怎么办?

本地存储

在命令开始执行时将命令的结果存储在客户端,您假设该命令将无错误地执行。如果后端在处理命令时遇到错误怎么办?那么你在本地的东西就不一致了。

轮询

轮询似乎是实际上符合最终一致性的选项;你不是假装或假设。您的轮询机制可以是异步的,作为页面的一部分,例如购物车页面组件轮询直到它得到更新而不刷新页面。

回调

您可以引入类似网络钩子的东西来向客户端进行回调如果客户端能够接收此类。通过在命令被后端接受后提供关联Id,一旦命令完成处理,后端可以将命令的状态以及命令是否成功通过的关联Id通知给前端.使用这种方法不需要任何类型的轮询。

【讨论】:

    猜你喜欢
    • 2020-06-10
    • 1970-01-01
    • 1970-01-01
    • 2013-02-03
    • 1970-01-01
    • 2018-12-20
    • 1970-01-01
    • 2015-06-05
    • 1970-01-01
    相关资源
    最近更新 更多