从整体式迁移到微服务时,如何处理外键约束?


18

我的团队正在从单一的ASP.NET应用程序迁移到.NET Core和Kubernetes。代码更改似乎正在进行中,并且可以预期,但是我的团队遇到的很多问题都围绕数据库进行。

当前,我们有一个相当大的SQL Server数据库,其中包含了整个业务的所有数据。我提议我们以与拆分代码类似的方式拆分数据库-一个逻辑数据库中的目录数据,另一个数据库中的库存数据,另一个数据库中的订单等-每个微服务都将成为其数据库的关守者。

这就意味着跨微服务边界的外键将必须被删除,跨边界的程序和视图将被禁止。所有数据模型可能会或可能不会驻留在同一个物理数据库中,但是即使它们存在,它们也不应直接相互交互。订单可能仍按ID引用目录项,但不会在数据库级别严格执行数据完整性,并且必须将数据以代码而不是SQL形式联接。

我认为这些损失是迁移到微服务并获得随之而来的可伸缩性优势时的必要折衷。只要我们明智地选择接缝并围绕它们发展,那应该没问题。其他团队成员坚持认为,所有内容都必须保留在同一个整体数据库中,以便所有内容都可以是ACID并在各处保留引用完整性。

这使我想到了我的问题。首先,我对外键约束和加入的立场是否合理?如果是这样,有人知道我可以提供给同事的任何可靠的阅读材料吗?他们的立场几乎是宗教性的,他们似乎不会因马丁·福勒本人告诉他们自己的错而受到任何影响。


5
参照完整性非常有价值。数据库的规模真的是这里的瓶颈吗?您是否真的需要微服务风格的可伸缩性?您比我更了解架构更改是否适合您的组织,但是请考虑一下,它根本不适合许多用例。可能还有其他方法可以通过更具吸引力的权衡进行扩展。例如,如果每秒数据库查询过高,则可能仅需要数据库复制。而且,您可以水平扩展Web服务器,而不必使用微服务。
阿蒙(Amon)

好点。我们正在寻找一些短期收益的选择。不过,转向微服务是漫长的过程。我认为,这将使我们可以扩展数年而不是数月。
雷蒙德·萨尔特雷利

3
我肯定您会为客户感到高兴,因为他们下令快0.05毫秒的订单会被取消,因为其他人在只剩一个库存时订购了相同的产品。
安迪

@amon将此作为答案,我会投票赞成。这是一个很好的问题,需要公平地代表利弊。
mcottle

@mcottle好的,完成了!
阿蒙(Amon)

Answers:


19

没有明确的解决方案,因为这完全取决于您的上下文,尤其是系统应该扩展到哪个维度以及实际问题是什么。数据库真的是您的瓶颈吗?

这个(不幸的是很冗长的)答案看起来像是“微服务是坏的,是一生的巨石!”,但这不是我的意图。我的观点是,微服务和分布式数据库可以解决各种问题,但必须解决一些问题。为了对您的体系结构提出有力的论据,您必须证明这些问题不适用,可以缓解,并且该体系结构是满足您的业务需求的最佳选择。

分布式数据很困难。

可实现更好缩放的相同灵活性是较弱保证的另一面。值得注意的是,分布式系统更加难的原因有关。

原子更新,事务,一致性/参照完整性和持久性是非常有价值的,不应轻易放弃。如果数据不完整,过时或完全错误,则毫无意义。当您将ACID作为业务需求但使用无法立即提供的数据库技术(例如,许多NoSQL数据库或每个微服务DB的数据库)时,您的应用程序必须填补空白并提供这些保证。

  • 这不是不可能的事,但是要正确就很难。非常棘手。尤其是在分布式环境中,每个数据库都有多个作者。这种困难意味着出现错误的可能性很高,可能包括数据丢失,数据不一致等等。

    例如,考虑阅读著名的分布式数据库系统Jepsen分析,也许是从Cassandra分析开始的。我不了解分析的一半,但TL; DR的缺点是分布式系统非常困难,以至于即使是行业领先的项目有时也会以在事后看来很明显的方式将它们弄错。

  • 分布式系统也意味着更大的开发工作。在一定程度上,开发成本或在功能更强大的硬件上花钱之间存在直接的权衡。

示例:悬挂参考

实际上,您不应该看计算机科学,而应该看您的业务需求,以了解是否可以放松ACID以及如何放松ACID。例如,许多外键关系可能并不像看起来那样重要。考虑一个产品-类别n:m关系。在RDBMS中,我们可以使用外键约束,以便只有现有产品和现有类别才能成为该关系的一部分。如果我们引入单独的产品和类别服务,并且删除了产品或类别会怎样?

在这种情况下,这可能不是什么大问题,我们可以编写应用程序,以过滤掉不再存在的任何产品或类别。但是,这里有一些权衡!

  • 请注意,这可能需要跨JOIN多个数据库/微服务的应用程序级别,这仅会将处理过程从数据库服务器移至您的应用程序。这增加了总负载,并且必须通过网络移动额外的数据。

  • 这可能会使分页混乱。例如,您请求一个类别中的下25个产品,然后从该响应中过滤出不可用的产品。现在,您的应用程序显示23个产品。从理论上讲,具有零产品的页面也是可能的!

  • 您可能需要偶尔运行一个脚本,该脚本在每次相关更改之后或定期进行清理悬挂的引用。请注意,此类脚本非常昂贵,因为它们必须从后备数据库/微服务请求每个产品/类别,以查看其是否仍然存在。

  • 这应该很明显,但是为了清楚起见:请勿重复使用ID。自动递增样式的ID可能正确也可能不合适。GUID或哈希为您提供了更大的灵活性,例如,可以在将项目插入数据库之前分配ID。

示例:并发订单

现在改为考虑产品-订单关系。如果产品被删除或更改,订单将如何处理?好的,我们可以简单地将相关产品数据复制到订单条目中以保持可用状态–为简化交易磁盘空间。但是,如果在订购该产品之前产品的价格发生变化或该产品不可用,该怎么办?在分布式系统中,效果需要花费时间才能传播,并且顺序可能会过时。

同样,如何解决此问题取决于您的业务需求。也许过时的订单是可以接受的,如果无法履行,您以后可以取消订单。

但这也许不是一个选择,例如对于高度并发的设置。考虑到在开始的10秒钟内有3000人争先恐后地购买音乐会门票,并且假设可用性的变化需要10毫秒才能传播。将最后一张票卖给多个人的概率是多少?取决于如何处理这些冲突,但是使用Poisson分布时,每10毫秒间隔就有一次发生冲突λ = 3000 / (10s / 10ms) = 3P(k > 1) = 1 - P(k = 0) - P(k = 1) = 80%机会。是否可以在不欺诈的情况下出售并取消大多数订单,可能会导致与您的法律部门进行有趣的对话。

务实意味着选择最好的功能。

好消息是,如果不需要这样做,则不必迁移到分布式数据库模型。如果您没有“适当地”做微服务,没有人会撤销您的微服务俱乐部会员资格,因为没有这样的俱乐部-也不存在构建微服务的真正方法。

实用主义每次都会取胜,因此在解决问题时会混合使用各种方法。这甚至可能意味着具有集中式数据库的微服务。确实,如果没有必要,不要经历分布式数据库的痛苦。

您可以在没有微服务的情况下进行扩展。

微服务有两个主要好处:

  • 它们可以由独立的团队独立开发和部署的组织利益(这反过来要求服务提供稳定的接口)。
  • 每个微服务可以独立扩展的运营优势。

如果不需要独立扩展,微服务的吸引力将大大降低。

数据库服务器已经是一种服务,您可以独立地进行扩展(某种程度上),例如通过添加只读副本。您提到存储过程。减少它们可能会产生巨大影响,以至于其他任何可伸缩性讨论都没有意义。

而且,拥有一个包含所有服务作为库的可伸缩单块是完全可能的。然后,您可以通过启动更多的整体实例来进行扩展,这当然要求每个实例都是无状态的。

除非整体组件太大而无法合理部署,或者某些服务有特殊的资源要求,以便您可能希望独立扩展它们,否则这通常会很好地起作用。涉及额外资源的问题域可能不涉及单独的数据模型。

您有强有力的业务案例吗?

您了解组织的业务需求,因此可以根据以下分析为每个微服务数据库架构创建参数:

  • 考虑到此类设置和替代解决方案的开发工作量不断增加,因此该架构是需要一定规模的,并且该架构是获得该可伸缩性的最具成本效益的方法;和
  • 您的业​​务需求允许放宽相关的ACID保证,而不会导致上述各种问题。

相反,如果您无法证明这一点,特别是如果当前的数据库设计能够支持对未来的足够扩展(正如您的同事似乎相信的那样),那么您也有自己的答案。

YAGNI还有一个很大的可扩展性组件。面对不确定性,这是一项战略性的业务决策,即现在就构建可伸缩性(降低总成本,但涉及机会成本,可能不需要),而不是推迟一些可扩展性工作(如果需要,则需要较高的总成本,但您会有更好的选择)实际规模的想法)。这主要不是技术决定。


很好的答案,谢谢。您能详细说明一下吗?您是说减少程序数量可能会对性能产生很大影响?您提到存储过程。减少它们可能会产生巨大影响,以至于其他任何可伸缩性讨论都没有意义。
阿兰

1
@alan可以很好地使用存储过程,但是它们会带来两个性能问题:(1)更复杂的查询对于数据库优化更困难。(2)使用sprocs意味着需要在数据库服务器上做更多的工作。OP希望拆分数据库以进一步扩展,但是避免使用复杂的存储程序可能已经提供了足够的空间。当然,存储过程和复杂查询也可以提高性能,例如,当存储过程和复杂查询最小化为查询响应而必须从数据库中移出的数据量时。当需要跨服务器的JOIN时,拆分数据库会使问题变得更糟。
阿蒙

0

我相信这两种方法都是可行的。您可以选择获得可扩展性,以牺牲ACID和单片数据库的优势,并与当前架构保持一致,并牺牲分布式架构的可扩展性和敏捷性。正确的决定将来自于当前的商业模式和未来几年的buz策略。纯粹从技术角度来看,将其保持为整体并转移到更分布式的方法是很麻烦的。我将分析系统,看看哪些应用程序/模块/业务流程对于扩展和评估风险,成本和收益更关键,以决定在整体架构中应等待或继续的风险。


-1

您的立场是合理和正确的。

然而,如何说服染羊毛的成瘾者是另一个问题。我会说你有两个选择。

  1. 查找数据库已达到其极限的具体示例。例如,您是否有“存档表”?为什么可以呢?您每秒可以接受的最大订单数量是多少?等。表明数据库不符合要求,您的解决方案将其修复。

  2. 雇用昂贵的承包商来告诉您最佳的解决方案。因为它们是费用,并且拥有博客,每个人都会相信


1
我不是-1,但要成为一个好答案,我会失去第2点的隐喻,并在需要多个数据库的时候进行扩展。我不认为存档表在不支持分区的数据库中不一定是反模式。我当前正在使用的数据库大约有130GB的数据,其中有26个表> 1000万行,并且性能不足以解决拆分数据库的问题。因此我非常怀疑,我想听听为什么这是个好主意,以及何时需要完成-这个答案是到目前为止我所看到的最接近的答案。
mcottle

好。我提到存档表是因为它们会破坏FK约束。它是盔甲的一个缝隙。通过微服务拆分并不是db大小的事情,这是使您的微服务可分离的事情。如果您不能关闭它并扔掉它,那实际上不是微服务。关于第二点。OP提到MF,他们可以从字面上雇用他/思想工作来告诉他们分裂数据库
Ewan

“如果您不能关闭它并扔掉它,那它实际上不是微服务。” 服务本身就是如此,但不一定是服务为什么需要它自己的数据库的论点。最终,数据库本身就是微服务正在使用的服务。微服务并不真正知道或不在乎其使用的数据是在单独的数据库中还是在共享数据库中。您可以向上或向下旋转此微服务的副本,并且什么都没有改变。
克里斯·普拉特

每个服务数据库的最佳参数是连接限制。使用连接池的情况并不少见,因此每个微服务已经需要到数据库实例的多个连接,然后您可以为每个微服务有多个实例,每个实例都有自己的池。最终,事情可能到了尽头,在这里您仅用尽了数据库处理所有正在获得的连接的能力。
克里斯·普拉特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.