数据库与微服务的一致性

时间:2015-06-28 20:42:03

标签: database soa microservices

在基于微服务的系统中实现数据库一致性的最佳方法是什么?

GOTO in Berlin,Martin Fowler谈论微服务,他提到的一条“规则”是保留“每服务”数据库,这意味着服务不能直接连接到另一个服务“拥有”的数据库

这是超级优雅和优雅,但在实践中它变得有点棘手。假设您有一些服务:

  • 前端
  • 订单管理服务
  • 忠诚度计划服务

现在,客户在您的前端进行购买,这将调用订单管理服务,这将保存数据库中的所有内容 - 没问题。此时,还会拨打忠诚度计划服务,以便从您的帐户中记入/记入积分。

现在,当所有内容都在同一个DB / DB服务器上时,一切都变得简单,因为您可以在一个事务中运行所有内容:如果忠诚度计划服务无法写入数据库,我们可以回滚整个事情。

当我们在多个服务中执行数据库操作时,这是不可能的,因为我们不依赖于一个连接/利用运行单个事务。 什么是保持事物一致并过上幸福生活的最佳模式?

我非常渴望听到您的建议!并提前致谢!

4 个答案:

答案 0 :(得分:11)

  

这是超级优雅和优雅,但在实践中它变得有点棘手

“在实践中”的含义是,您需要设计您的微服务,以便在遵循规则时满足必要的业务一致性:

  

服务不能直接连接到另一个服务“拥有”的数据库。

换句话说 - 不要对他们的责任作出任何假设,并根据需要改变界限,直到找到一种方法使其发挥作用。

现在,问你的问题:

  

保持事物一致并过上幸福生活的最佳模式是什么?

对于不需要立即一致性的事情,并且更新忠诚度积分似乎属于该类别,您可以使用可靠的发布/订阅模式从一个微服务调度事件以供其他人处理。可靠的一点是,您需要为事件处理提供良好的重试,回滚和幂等性(或事务性)。

如果您在.NET上运行,那么支持此类可靠性的基础架构示例包括NServiceBusMassTransit。完全披露 - 我是NServiceBus的创始人。

更新:关于忠诚度积分问题的评论:“如果延迟处理余额更新,客户实际上可以订购的商品数量多于他们的积分”。

许多人为了强烈的一致性而努力满足这些要求。问题在于,通常可以通过引入其他规则来处理这些情况,例如,如果用户最终通过负忠诚点通知他们。如果T在没有忠诚点被分类的情况下经过,则通知用户他们将根据某些转换率向他们收费M.客户在使用积分购买东西时应该可以看到此政策。

答案 1 :(得分:7)

我通常不会处理微服务,这可能不是一种好的做事方式,但这是一个想法:

为了重申这个问题,该系统由三个独立但相互沟通的部分组成:前端,订单管理后端和忠诚度计划后端。前端希望确保在订单管理后端和忠诚度计划后端中保存一些状态。

一种可能的解决方案是实现某种类型的two-phase commit

  1. 首先,前端将记录放在自己的数据库中,包含所有数据。将此名称称为前端记录
  2. 前端向订单管理后端询问交易ID,并将其传递给完成操作所需的任何数据。订单管理后端将此数据存储在暂存区域中,并将新数据关联并将其返回到前端。
  3. 订单管理交易ID存储为前端记录的一部分。
  4. 前端向忠诚度计划后端询问交易ID,并将其传递给完成操作所需的任何数据。忠诚度计划后端将这些数据存储在临时区域,并将新数据关联并将其返回到前端。
  5. 忠诚度计划交易ID存储为前端记录的一部分。
  6. 前端告诉订单管理后端完成与前端存储的事务ID相关联的事务。
  7. 前端告诉忠诚度计划后端完成与前端存储的交易ID相关联的交易。
  8. 前端删除其前端记录。
  9. 如果实现了这一点,那么更改不一定是 atomic ,但它将最终一致。让我们想想它可能失败的地方:

    • 如果第一步失败,则不会更改数据。
    • 如果在第二个,第三个,第四个或第五个失败时,当系统重新联机时,它可以扫描所有前端记录,查找没有关联事务ID(任何一种类型)的记录。如果遇到任何此类记录,它可以从步骤2开始重播。(如果步骤3或5中出现故障,后端会留下一些遗弃的记录,但它永远不会移出暂存区域,所以没关系。)
    • 如果在第六步,第七步或第八步失败,当系统重新联机时,它可以查找填写了两个交易ID的所有前端记录。然后可以查询后端以查看这些交易的状态 - 已提交或未提交。根据已提交的内容,可以从适当的步骤恢复。

答案 2 :(得分:0)

我同意@Udi Dahan所说的话。只是想补充一下他的答案。

我认为您需要将请求保留到忠诚度计划中,这样如果失败,可以在其他方面完成。有多种方法可以说/做到这一点。

1)使忠诚度计划API失败可恢复。也就是说它可以持久保存请求,以便它们不会丢失,并且可以在稍后的某个时间恢复(重新执行)。

2)异步执行忠诚度计划请求。也就是说,首先在某处保留请求,然后允许服务从此持久存储中读取它。仅在成功执行时从持久存储中删除。

3)做Udi所说的,把它放在一个好的队列上(确切地说是pub / sub模式)。这通常要求订户执行以下两种操作之一...要么在从队列中删除之前保留请求(转到1) - 或者 - 首先从队列中借用请求,然后在成功处理请求之后,请求请求从队列中删除(这是我的偏好)。

这三个人完成了同样的事情。他们将请求移到一个持久的地方,在那里可以继续工作直到成功完成。请求永远不会丢失,必要时重试,直到达到满意的状态。

我喜欢用接力赛的例子。在允许前一段代码放弃之前,每个服务或代码段必须保留并拥有该请求。一旦它被移交,当前所有者不得丢失请求,直到它被处理或移交给其他一些代码。

答案 3 :(得分:0)

即使对于分布式事务,如果其中一个参与者在事务中崩溃,您也可以进入“处于疑问状态”。如果您将服务设计为幂等操作,那么生活就会变得容易一些。人们可以编写程序来满足没有XA的业务条件。帕特·赫兰德(Pat Helland)撰写了一篇名为“Life Beyond XA”的优秀论文。基本上,该方法是尽可能地做出关于远程实体的最小假设。他还阐述了一种称为开放嵌套事务(http://www.cidrdb.org/cidr2013/Papers/CIDR13_Paper142.pdf)的方法来建模业务流程。在这种特定情况下,购买交易将是顶级流程,忠诚度和订单管理将是下一级流程。诀窍是将粒度服务作为具有补偿逻辑的幂等服务。因此,如果任何事情在流程中的任何地方失败,个别服务可以补偿它。所以例如如果订单因某种原因失败,忠诚度可以扣除该购买的累计积分。

其他方法是使用CALM或CRDT使用最终一致性进行建模。我写了一篇博客来强调在现实生活中使用CALM - http://shripad-agashe.github.io/2015/08/Art-Of-Disorderly-Programming可能会帮助你。