【问题标题】:In microservices, should a data be store in one service or duplicated across services?在微服务中,数据应该存储在一个服务中还是跨服务复制?
【发布时间】:2020-06-26 22:33:10
【问题描述】:

我有一个Scheduler 服务,它允许我添加计划任务。该服务仅在其数据库中保存触发任务时要调用的调度时间和端点。

例如,有一个Payment 服务和一个EmailReminder 服务。 Payment服务可以为Scheduler服务添加定期支付的定时任务。 EmailReminder 服务可以为Scheduler 服务添加邮件提醒任务。当任务在Scheduler服务中触发时,它们将被标记为done并发送到相应服务的端点以处理任务。

在前端,它必须显示预定电子邮件提醒任务的所有信息,例如收件人电子邮件,电子邮件内容以及是否已完成。前端将从位于EmailReminder 服务中的 API 中提取大部分信息。这很简单,因为它需要从自己的数据库中显示在 API 中的所有数据。

然而,我的困境是我应该将done 状态保留在Scheduler 服务的数据库中,还是将该状态保存在各个服务自己的数据库中。

如果状态存储在Scheduler 服务中...

如果我在Scheduler 服务中保持“完成”状态,那么每当其他服务需要知道某项任务是否完成时,它们必须对Scheduler 服务进行 API 调用。换句话说,每次从前端调用其 API 时,EmailReminder 服务必须从Scheduler 服务获取所有记录的“完成”状态。我相信这也会在整体请求上产生额外的时间。但这样做的好处是Scheduler 服务中的数据库是判断任务是否完成的唯一真实来源。

如果状态存储在相应的服务中...

在这种情况下,EmailReminder 服务不需要对Scheduler 服务进行额外的 API 调用。该信息可在其自己的数据库中获得。这也意味着当Scheduler 服务中的done 状态发生变化时,它必须调度一个事件来通知所有服务更新它们的状态。然而,这样做的缺点是,我基本上在两个地方复制了同一条信息(done 状态); Scheduler 服务和 EmailReminder 服务。存在数据不一致的风险。

在像我这样的微服务架构中,将此类信息存储在Scheduler 服务中还是在相应的服务中更好?

【问题讨论】:

    标签: node.js database-design architecture domain-driven-design microservices


    【解决方案1】:

    在微服务架构中,一般来说,共享数据应该只包含非敏感的唯一标识符或密钥。微服务应该只存储服务所需要的数据,这些数据按照定义的有界上下文进行处理。

    【讨论】:

      【解决方案2】:

      EmailReminder 是唯一知道电子邮件何时发送的实体(即任务是done)。

      使用事件驱动的方法,EmailReminder 可以触发done 事件,该事件可以被系统中的所有其他参与者拦截。这样,每个人都可以拥有一份信息副本。从性能和弹性的角度来看,它是好的,只要您处理缺点(定义特定数据集的主控、最终一致性等)。

      您需要评估这是否适用于您的系统。

      祝你的项目好运。

      【讨论】:

        【解决方案3】:

        一个关于协同处理的简单问题。模块生产的产品应该存在一个位置,每个模块应该负责知道是否处理它。换句话说,“完成”状态是最终产品的一列,而不是任何正在处理它的特定服务。

        您的“服务数据库”是每个服务的状态,应该独立于最终产品建模。您已经构建了流程,但没有定义产品的标题并将其单独存储。

        【讨论】:

        • 那么,我可以说你的意思是done 状态应该只驻留在调度程序服务的数据库中,而不应该驻留在其他服务的数据库中,对吗?如果其他服务想知道某项任务是否完成,它们会向调度程序服务发出请求以查找它,这是您的意思吗?
        • 是的,其他服务产生完成状态,这是调度程序记录的一部分。但是,他们应该有权直接读取/写入该数据到调度程序的记录中,因为该记录是产品。如果您想做到完美,交易系统分类帐自然会记录每次更改以及由谁进行的更改。
        【解决方案4】:

        一般来说,我建议您不要缓存/复制数据,除非您有理由这样做。缓存失效被认为是one of the two hard thing in Computer Science。让下游服务订阅事件也会增加复杂性,从而增加总体成本。请注意,我是事件驱动架构/消息传递的超级粉丝——我只是相信你应该在何时何地使用它时保持谨慎。

        在我看来,

        。 . .每当其他服务需要知道某项任务是否完成时,它们都必须对调度程序服务进行 API 调用。

        不一定是否定的。

        如果这些调用非常频繁和/或正在导致(或可能导致)性能问题,那就另当别论了,缓存非常有意义。

        【讨论】:

          【解决方案5】:

          这是一个正确的困境,我认为没有完美的答案。每个解决方案都会有一些权衡。在某些时候,它也归结为Fat 事件与Thin 事件。

          Fat 事件场景中,您将让Status 信息与消息一起传播,而在Thin 事件中,您将只发送事件而不发送数据。

          让我们说,您的Scheduler 服务上有许多依赖服务。如果您选择在没有额外数据(或状态信息)的情况下触发您的事件,您可能会让所有这些服务调用您的 API,要求更新的 Status 导致服务的额外负载。虽然这可能会因为通过网络进行额外的调用而产生性能成本,但除非您正在处理时间关键的应用程序,否则我认为这不是一个真正的问题。您还可以选择在服务器上缓存响应以避免 DB 调用。

          如果您选择Fat 事件,那么由于您将“状态”信息与您的事件一起发送,因此您不需要额外的 API 调用。但是,这确实意味着您需要额外的存储空间来在每个服务中存储“状态”。但是,存储是一种商品,它很便宜,可能不应该是不使用 FAT 事件的原因。当然,这里存在一个风险,即如果某个订阅者以某种方式错过了该事件怎么办。然而,事件驱动架构带来了这些风险,您需要使您的服务能够容错这些问题。 KafkaNServiceBus 等平台可以帮助您降低这些风险。

          【讨论】:

            【解决方案6】:

            我认为您应该在这两个服务中保留该信息。

            假设Scheduler 请求EmailReminder 发送ID 为#123 的电子邮件,EmailReminder 发送电子邮件并在本地保持“发送电子邮件#123”状态,但确认失败( Scheduler 没有收到响应或无法持久化响应)。现在Scheduler别无选择,只能重复请求。但是,当EmailReminder 收到请求时,它通过自己的状态知道邮件已经发送,并且服务可以安全地不发送邮件,而只是回复确认 - 这一次希望可以工作。

            Scheduler 收到响应时,它将在本地保持“计划任务 #123 已完成”状态 - 并停止重复请求。

            这也意味着当调度器服务中的完成状态发生变化时,它必须调度一个事件来通知所有服务更新它们的状态。

            Scheduler 怎么能改变它的完成,如果不是 EmailReminder 服务告诉它,它已经完成了?

            存在数据不一致的风险。

            是的,这里的数据不一致意味着需要重试请求。

            需要注意的一点是,EmailReminder 服务本身也存在相同的不一致问题,因为可以发送电子邮件并且错误会阻止本地状态正确更新。因此,电子邮件总是有可能被发送两次(“至少一次”发送,或者您可以通过在发送前更改状态来选择“最多一次”) - 但这种方式风险较低。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-09-04
              • 1970-01-01
              • 2021-01-11
              • 2015-06-10
              • 2020-01-31
              • 2015-09-03
              • 2012-01-21
              相关资源
              最近更新 更多