响应式微服务_低风险整体式微服务演进第一部分

响应式微服务

作为为期两天的微服务研讨会的一部分,我一直在思考如何解释整体应用分解以及向微服务过渡的方式。 这只是该材料的一小部分,但我想与您分享以获取反馈(在研讨会上,我们将更详细地介绍您是否应该拆开整体!)。 这是基于我自己尝试过的真实生活经验,以及我与过去几年在北美遇到的许多Red Hat客户的合作。 第一部分探讨了体系结构,而第二部分(即将发布)将介绍一些可以在此领域大有帮助的技术。 在Twitter或http://blog.christianposta.com上关注( @christianposta ),以获取最新更新和讨论。

在深入研究之前,我们先建立一些假设。

  • 微服务架构并不总是适合(需要详细讨论)
  • 如果发现应该使用微服务架构,则必须决定如何处理整体组件
  • 在极少数情况下,我们将按原样拆分整体部分; 在大多数其他情况下,我们将希望围绕整体构建新功能或重新实现现有业务流程(以勒死方式)
  • 在我们需要突破功能或重新实现的情况下,我们不能忽略这样一个事实,即整体生产目前正在承受负担,并可能带来很多业务价值
  • 我们需要一种在不破坏系统总体业务价值的情况下解决此问题的方法
  • 由于整体是整体,因此对其下的数据模型/数据库进行更改将非常困难/几乎不可能
  • 我们采用的方法应降低发展的风险,并可能需要进行多次部署和发布才能实现一切

提取微服务

如果您深入研究此主题的会议/博客文章,通常您会发现它提供了以下建议:

  • 围绕名词组织
  • 做好一件事
  • 单一责任原则
  • 这个很难(硬

恐怕这个建议是没有用的。

更有用的材料讨论了有时看起来像这样的方法:

  • 识别模块(要编写的现有模块或新模块)
  • 分解对应于那些模块的表并包装服务
  • 直接更新曾经直接依赖于数据库表的代码以调用此新服务
  • 冲洗并重复

让我们仔细看看:

步骤1:识别模块

您从一些讨厌的巨石开始。 在上图中,我已对此进行了简化,以表示此处可能涉及的不同模块和数据库表。 我们确定希望从整体中突破的模块,并确定涉及哪些表,然后从那里开始。 当然,现实是,整体结构与彼此混杂的模块(如果有的话)纠缠得多。 稍作讨论。

步骤2:分解数据库表,包装服务,更新依赖关系

下一步是确定Foo模块使用哪些表,并将这些表分解为自己的服务。 现在,此服务是唯一可以访问那些Foo表的东西。 没有更多的共享表! 这是一件好事。 曾经提到Foo现在都必须通过新创建的服务的API。 在上图中,我们更新了Bar and Cheese服务,现在在需要Foo东西时引用Foo服务。

步骤3:冲洗并重复

最后一步是重复这项工作,直到您不再拥有整体。 在上图中,我们对Bar服务做了同样的事情,您可以看到我们正在转向一种架构,在该架构中,服务拥有数据并公开API,这听起来与我们所听到的微服务类似。

尽管此方法通常是一套不错的指南, 但它错过了我们沿着这条道路真正需要的许多保真度 。 例如,这种方法掩盖了一个事实,即我们不能止步于从数据库中删除表的世界。 也:

  • 整体结构很少能够很好地进行整洁的模块化
  • 表之间的关系可以高度规范化,并在实体之间表现出严格的耦合/完整性约束
  • 我们可能不完全了解Monolith中的哪些代码使用哪些表
  • 仅仅因为我们已将表提取到新服务并不意味着现有业务流程停止,所以我们可以迁移所有人以使用该服务
  • 将会有一些丑陋的迁移步骤,这是不希望的
  • 可能存在收益递减的问题,将事情从整体中分解出来是没有意义的

–等等,等等

让我们看一个具体的示例,以及方法/模式的外观以及我们可能使用的选项。

具体例子

此示例来自上述研讨会。 我将在服务拆分方面添加一些颜色,但是我们将在本次研讨会中讨论的领域驱动设计,耦合模型和物理/逻辑体系结构方面有更详细的指导。 表面上的这种方法似乎可以处理现有整体功能的分解,但同样适用于在整体周围添加新功能; 这可能是更可能的情况,因为更改整料会非常冒险。

遇见巨石

这就是我们将要探索的Monolith 。 它基于developers.redhat.comTicketMonster教程。 该教程探讨了如何构建典型的Java EE应用程序,并最终成为一个很好的例子:它并不太复杂,但是有足够的基础,我们可以用它来说明关键点。 在此博客文章的第二部分中,我们实际上将更深入地研究技术框架/平台。

从该图像中,我们说明了Monolith具有与单个Monolitic数据库的共同部署的所有模块/组件/ UI。 每当我们需要进行更改时,我们都需要部署所有更改。 您可以想象一下,该应用程序已有10多年的历史,现在很难更改(出于所有技术原因,还有团队/组织结构的原因)。 我们希望突破UI和关键服务,使业务能够更快且彼此独立地进行更改,以尝试提供新的客户/和/或/业务价值。

注意事项

  • 整体式(代码和数据库架构)很难更改
  • 变更需要完全重新部署并在团队之间进行高度协调
  • 我们需要进行大量测试以赶上回归
  • 我们需要一种完全自动化的部署方式

提取UI

在此步骤中,我们将使UIMonolith分离。 实际上,在这种体系结构中,我们实际上并没有从Monolith删除任何内容。 我们仅通过添加包含UI的新部署开始降低风险。 此体系结构中的新UI组件应非常接近Monolith的同一UI(完全是?),并仅回调Monolith的REST API…当然,这意味着Monolith具有合理的API,外部UI可以用。 我们可能会发现情况并非如此:通常,这种类型的API可能更像是一个“内部” API,这时我们需要考虑单独的UI组件和后端整体之间的某种集成,以及可能会使用更多可消费的面向公众的API。

我们可以将这个新的UI组件部署到我们的体系结构中,并使用我们的平台将流量缓慢路由到它,同时仍然路由到旧的整体。 这样,我们可以在不停机的情况下引入它。 同样,在博客文章的第二部分中,我们将详细介绍如何执行此操作。 但是, 暗启动/金丝雀发布/滚动发布的概念在这里(以及后续步骤)都非常重要。

注意事项

  • 第一步,请勿修改整体。 只需将UI复制/粘贴到单独的组件中
  • 我们需要在UI和整体之间建立一个合理的远程处理API-并非总是如此
  • 安全面增加
  • 我们需要一种以受控方式将流量路由/拆分到新用户界面和/或整体的方式,以支持黑暗发射/金丝雀/滚动释放

从整体中删除UI

在上一步中,我们引入了一个UI并将流量缓慢地转移到新UI (该新UIMonolith直接通信)。 在此步骤中,我们执行类似的部署策略,不同之处在于,现在我们缓慢发布释放了UI的整体的新部署。 如果发现问题,我们可以缓慢地释放流量并倾斜/回滚。 一旦我们拥有所有的流量将我们的整体式无UI(称为Backend从这里开始),我们可以删除Monolith完全部署。 通过分离出UI,我们现在对整体程序进行了小规模分解,并通过依赖于黑暗的启动/ canary / rolling版本降低了风险。

注意事项

  • 我们正在从整体中删除UI组件
  • 这就要求(希望如此)对整体的最小更改(不赞成使用/删除/禁用UI)
  • 再次,我们使用受控的路由/整形方法来引入此更改而无需停机

推出一项新服务

在下一步中,省略了耦合,域驱动设计等的详细信息,我们将引入一项新服务: Orders服务。 这是一项关键服务,企业希望比其他应用程序更频繁地进行更改,并且具有相当复杂的写入模型。 我们也可以使用此模型探索诸如CQRS的架构模式,但我离题了。

我们希望根据Backend中的现有实现来关注Orders服务的边界和API。 实际上,与现有代码的移植相比,此实现更有可能是重写的,但是此处的想法/方法是相同的。 注意,在这种体系结构中, Orders服务具有其自己的数据库。 很好 我们正在为完全脱钩而努力-但是我们还没有实现。 我们还需要考虑/采取一些其他步骤。

这一步也是通过关注其可能发出或消耗的事件来考虑此服务在整个服务体系结构中如何发挥作用的好时机。 现在是进行像Event Storming这样的活动的好时机,并仔细考虑在开始处理事务性工作负载时应该发布的事件。 当我们尝试与其他系统集成时,甚至在演化整体时,这些事件都将派上用场。

  • 我们想专注于提取服务的API设计/边界
  • 这可能是对巨石中存在的东西的重写
  • 确定API后,我们将为该服务实现一个简单的脚手架/占位符
  • 新的订单服务将拥有自己的数据库
  • 新的订单服务目前将不会进行任何形式的交易

将API与实现连接

在这一点上,我们应该继续发展服务的API和域模型以及如何在代码中实现它。 该服务会将所有新的事务性工作负载存储到其自己的数据库中,并将其与其他任何服务分开。 需要访问此数据的服务将必须通过API。

我们将无法忽略的一件事:我们的新服务及其数据与整体中的数据密切相关(如果在某些方面不是完全相同)。 实际上,这非常不方便。 在开始构建新服务时,我们将需要Backend服务数据库中的现有数据。 由于数据模型中的规范化/ FK约束/关系,这可能会变得非常棘手。 在整体/后端上重新使用现有的API可能过于粗糙,我们必须重新发明大量的体操才能获得所需形状的数据。

我们要做的是通过较低级别的API以只读模式从Backend访问数据,并有一种方法将数据/数据模型成形为更适合我们新版域模型的模型。服务。 在这种体系结构中,我们将直接临时连接到Backend数据库,并根据需要直接查询数据。 对于此步骤,我们确实需要一个反映直接数据库访问的一致性模型。

你们当中有些人可能最初会畏缩于这种方法。 那你应该 但是,显而易见的事实是,这种方法是完全有效的,并且已经在高度关键的系统中成功使用-更重要的是-不是最终状态体系结构(如果您认为这最终可能会成为最终状态体系结构,请停止并执行请勿这样做)。 您可能还会指出,连接到Backend数据库,查询数据以及将其调整为新服务的域模型所需的正确形状涉及许多麻烦的集成/模板。 我认为这是我们整体发展中的暂时现象,因此可能还可以:即,利用技术债务为自己谋取利益,然后Swift偿还。 但是,有更好的方法。 我将在此博客文章的第2部分中对此进行讨论。

或者,您中的某些人可能会说:“好吧,只要在Backend数据库前面建立一个REST API,它就可以提供较低级别的数据访问权限,并可以进行新的服务调用”。 这也是一种可行的方法,但并非没有缺点。 同样,将在第2部分中对此进行更详细的讨论。

注意事项

  • 提取的/新的服务具有一个数据模型,根据定义,该数据模型与整体数据模型紧密耦合
  • 整体很可能​​没有提供正确级别的API来获取此数据
  • 即使获得数据也要整形数据需要大量样板代码
  • 我们可以暂时直接连接到后端数据库以进行只读查询
  • 整体数据库很少(如果有的话)更改其数据库

开始将影子流量发送到新服务(暗启动)

在此方法/模式的下一步中,我们需要一种将流量实际定向到我们的新服务的方法。 但是,这里的关键是我们不想大肆发布。 我们不想仅仅将其投入到我们的生产流量中(特别是考虑到该示例使用了接受订单的“订单”服务!我们不想在接受订单时引入任何问题!)。 尽管我们不能轻易更改基础的整体数据库,但如果有希望,您也许可以仔细地对整体应用程序进行更改,以调用我们的新订单服务。 如果您不确定如何最好地做到这一点,我强烈建议迈克尔·费瑟(Michael Feather)的“有效使用旧版代码” 。 有一些模式,例如“发芽方法/类”和/或“包装方法/类”可以在这里提供帮助。

当我们更改整体/后端时,我们不想替换旧的代码路径。 我们希望把足够的代码同时允许旧的或新的代码路径运行,甚至可能并行,认为合适。 理想情况是,经过此更改的整体式新版本将使我们能够在运行时控制是要发送到新的Orders服务,旧的整体式代码路径还是两者。 在调用路径的任何组合中,我们都想通过大量手段来了解新旧执行路径之间的任何潜在偏差。

另一件事要注意。 如果我们使整体能够将执行发送到旧代码路径以及调用我们的新服务,则需要一种方法来将该事务/对新服务的调用标记为“综合”调用。 如果您的新服务不如本例中的订购服务那么关键,并且您可以处理重复项,那么此综合请求标识可能就不那么重要了。 如果您的新服务倾向于提供更多的只读流量,那么您可能不必再担心识别合成事务。 但是,在并发事务的情况下,您希望能够端到端运行整个服务,包括存储到数据库中。 您可以在此处选择使用“合成”标志存储数据,或者如果您的数据存储支持该事务则仅回滚事务。

这里要注意的最后一件事是,当我们对整体/后端进行更改时,我们希望再次使用暗启动/金丝雀/滚动释放方法。 我们将需要我们的基础架构来支持这一点。 我们将在第二部分中对此进行更仔细的研究。

在这一点上,我们正在迫使流量返回通过整体。 我们正在尝试尽可能不干扰主呼叫流程,以便在金丝雀运行不佳的情况下可以快速回滚。 另一方面,部署网关/控制组件可能会很有用,该组件可以更精细地控制对新服务的调用,而不是强制执行整体。 在这种情况下,网关将具有控制逻辑,是将事务发送到整体,新服务还是同时发送。

注意事项

  • 将新的Orders服务引入代码路径会带来风险
  • 我们需要以受控方式将流量发送到新服务
  • 我们希望能够将流量引导到新服务以及旧代码路径
  • 我们需要检测和监视新服务的影响
  • 我们需要将交易标记为“综合”交易的方式,因此我们不会陷入麻烦的业务一致性问题
  • 我们希望将此新功能部署到某些同类/组/用户

金丝雀/滚动发布到新服务

如果我们发现上一步未对交易路径造成负面影响,并且通过对流量进行屏蔽的生产测试和初始实验具有很高的信心,那么我们现在可以将整体配置为“不屏蔽”,然后将流量实际发送到新服务。 在这种情况下,我们需要能够指定某些队列/组/用户以始终使用新服务。 在这种情况下,我们将慢慢消耗通过旧代码路径的实际生产流量。 我们可以增加Backend服务的滚动发布,直到所有用户都使用新的Orders微服务。

我们需要具体说明一个风险点:一旦开始将实时流量(非影子/合成流量)转移到新服务上,我们期望与同类群组匹配的用户将始终使用该新服务。 我们无法在新旧代码路径之间来回切换。 在这种情况下,如果我们确实需要回滚,则将涉及更多的协调,以将任何新事务从新服务移回到旧的整体可以使用的状态。 希望这不会发生,但是需要注意和计划(和测试!)。

注意事项

  • 我们可以识别同类群组并将实时交易流量发送到我们的新微服务
  • 我们仍然需要直接连接到整体数据库,因为在一段时间内,交易仍将同时到达两个代码路径
  • 将所有流量移至微服务后,我们应该可以淘汰旧功能
  • 请注意,一旦我们将实时流量发送到新服务,就必须考虑到回滚到旧代码路径将涉及一些困难和协调这一事实。

离线数据ETL /迁移

此时,我们应该让我们的Orders微服务接收实时生产流量。 MonolithBackend仍在处理其他问题,但是我们已经成功地将服务功能迁移到Monolith之外。 现在,我们迫切需要解决的问题是,当我们在新的微服务和Backend服务之间建立直接数据库连接时,偿还借入的技术债务。 这很可能涉及从整体数据库到新服务的某种一次性ETL。 可能会要求独石将数据保持为只读模式(请考虑:法规遵从性等)。 如果这是共享的参考数据(即只读),则应该可以。 我们必须确保整体中的数据和新微服务中的数据不是共享的可变数据。 如果数据是共享/可变的,那么我们可能会遇到不同的数据/数据所有权问题。

注意事项

  • 现在,我们新的Orders微服务正处于完全自动化的最后阶段
  • 当我们将Orders服务数据库连接到Backend数据库时,我们需要偿还我们欠下的技术债务
  • 我们将为应该驻留在Orders服务中的数据提供一次性ETL
  • 我们需要注意分散的数据问题

断开/解耦数据存储

完成上一步后,我们应该使新的Orders微服务独立,并准备参与服务体系结构。 这里介绍的步骤各有各的利弊。 我们的目标是满足所有步骤,而不要留下技术债务来产生利息。 当然,这种模式会有所不同,但是方法是正确的。

在下一篇博客文章中,我将展示如何使用我之前提到的示例服务来执行这些步骤,并更深入地研究有助于提供帮助的一些工具,框架和基础结构。 我们将研究诸如KubernetesIstio ,功能标记框架,数据视图工具和测试框架之类的东西。 敬请关注!

翻译自: https://www.javacodegeeks.com/2017/09/low-risk-monolith-microservice-evolution-part.html

响应式微服务

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值