17611538698
webmaster@21cto.com

是时候回到单体应用了吗?

架构 0 1551 2023-02-20 10:57:06

图片

导读:现代模块化单体(和 Moduliths)正在重新定义新的单体架构,而使用混合方法的选择是两全其美的方法。

历史总是在重演,一切旧的东西又变成新的。我们已经存在了足够长的时间,会看到有的想法被丢弃,又被重新发现,并胜利地回归并超越时尚。

近年来,SQL 涅槃重生了,人们也再次喜欢关系数据库。我认为单体将再次出现太空漫游,而微服务和无服务器只是云供应商推动的趋势,它们的目的是向我们出售更多的云计算资源。

对于大多数用例而言,微服务在财务上意义并不是很大。是的他们还能再减少。当扩大规模时,他们会以分利的形式分摊这些成本。

我最近召开了一个技术小组会议,讨论了微服务与单体应用的主题。小组中的共识(即使是支持单体应用的人)只是单体应用架构的扩展性不如微服务。

它正用在亚马逊、eBay 等古老的庞然大物中。要更换它,可那些确实是巨大的代码库,其中的每一次修改都将是痛苦的,并且它们的扩展具有挑战性。

这可不是一个公平的比较,较新的方法通常会击败旧方法。但是如果我们使用更新的工具构建单体应用,我们能获得更好的可扩展性吗?

局限性是什么?现代单体看起来是什么样的?

图片

模块化


要了解后一部分,各位开发者们可以先查看Spring Modulith项目。


它是一个模块化的整体,让我们可以使用动态隔离的部分构建一个整体。通过这种方法,我们可以将测试、开发、文档和依赖项分开。这有助于微服务开发的隔离,并且涉及的开销将很少。


并且,它消除了远程调用和功能复制(存储、身份验证等)的开销。


Spring Modulith 并非基于Java 平台模块化 (Jigsaw)。它是在测试期间和运行时强制执行分离,这是一个常规的 Spring Boot 项目。它具有一些用于模块化可观察性的额外运行时功能,执行“最佳实践”。这种分离的价值超出了我们通常使用微服务的价值,但也需要有一些权衡。 


让我们举个例子。传统的 Spring 单体应用具有如下分层的包架构:


com.debugagent.myappcom.debugagent.myapp.servicescom.debugagent.myapp.dbcom.debugagent.myapp.rest

这是很有价值的设计,因为它可以帮助我们避免不同层之间的依赖关系;

例如,数据库层不应该依赖于服务层。我们可以使用模块并有效地强制依赖朝一个方向发展。

但随着产品和运营的成长,这种情况的意义也会降低——每一层也都将充满业务逻辑类和数据库的复杂性。 

当使用 Modulith,我们的架构看起来更像这样:

com.debugagent.myapp.customerscom.debugagent.myapp.customers.servicescom.debugagent.myapp.customers.dbcom.debugagent.myapp.customers.rest
com.debugagent.myapp.invoicingcom.debugagent.myapp.invoicing.servicescom.debugagent.myapp.invoicing.dbcom.debugagent.myapp.invoicing.rest
com.debugagent.myapp.hrcom.debugagent.myapp.hr.servicescom.debugagent.myapp.hr.dbcom.debugagent.myapp.hr.rest

这看起来非常合适微服务架构。

我们根据业务逻辑将所有部分都分开。在这里,可以更好地包含交叉与依赖关系,团队可以专注于自己的区域并隔离,不会踩到彼此的脚趾头。

这是微服务的价值,而且也没有额外开销。

我们可以用注解声明的方式进一步加强分隔。可以定义哪个模块在使用,哪个强制单向依赖,比如人力资源模块与发票无关,客户模块也不会与其它模块有什么关联。

我们也可以在客户和发票之间强制建立一种单向关系,并使用事件进行反馈。在 Modulith 中的事件是快速的并且是事务性的,可以毫不费力地解耦模块之间的依赖关系。

这可以与微服务有关,但很难实施。假设发票需要向不同的模块公开接口。人们如何防止用户调用该接口?

使用模块,我们却是可以做得到的。是的,用户可以更改代码并提供访问权限,但这需要通过代码审查,也会带来自己的问题。

请注意,对于使用模块,我们仍然可以依赖常见的微服务主要部件,例如功能标志、消息系统等。我们可以在文档和Nicolas Fränkel 的博客中阅读有关 Spring Modulith 的相关内容。

在模块系统中,每个依赖项都将被映射并记录在代码中。Spring 的实现包括使用方便的最新图表和自动记录内容的能力。

你可能会认为,依赖性是 Terraform 的原因,这种“高级”设计适合放在那儿吗?是的,像 Terraform 这样的基础设施即代码 (IaC) 解决方案仍然可以可用于 Modulith 单体部署,但它们会简单得多。

问题作为责任的划分。单体应用的复杂性并没有随着微服务而消失,如下图所示(取自线程相关)。开发者们只是把这堆蠕虫般的代码踢给了 DevOps 团队,这让他们的生活更加艰难。更糟糕的是,我们没有给他们提供正确的工具来理解这种复杂性,因此他们必须从外部进行管理。

图片

这就是现在为什么IT行业的基础设施成本还在上升,而传统上价格应该呈下降趋势才对。当 DevOps 团队遇到问题时,他们会投入大量资源,这并非在所有情况下都是正确的做法。

其它模块


我们可以使用标准 Java 平台模块 (Jigsaw) 来构建 Spring Boot 应用程序。这具有分布型应用程序和标准 Java 语法的优点,但有时可能会很尴尬,这可能在使用外部库或将一些工作拆分到通用工具中的效果最好。


另一种选择是Maven 中的模块系统。


这个系统让我们把构建分成多个独立的项目。这是一个非常方便的过程,使我们免于庞大项目的麻烦。每个项目都是独立的,易于使用。它可以使用自己的构建过程。然后当我们构建主项目时,一切又都变成了一个整体。在某种程度上,这正是我们许多人真正想要的东西。


规模


我们可以使用大多数微服务工具来进行扩展工作。大量与扩展、集群相关的研究都是在考虑单体应用的情况下开发的。这是一个更简单的过程,因为只有一个活动部分:应用程序。我们复制其它实例并观察它们,没有失败的个别服务。我们有细粒度的性能工具,一切都作为一个统一的版本运行。


我认为扩展比等效的微服务更简单。


我们可以使用分析工具获得合理的瓶颈近似值,我们的团队可以轻松且经济地设置暂存环境来运行测试。我们拥有整个系统及其依赖项的单一视图,也可以单独测试单个模块并验证性能假设。


跟踪和可观察性工具非常优秀,但它们也会影响生产环境,有时还会产生噪音。当我们试图解决扩展瓶颈或性能问题时,它可能会让我们陷入一些错误。


此时可以将 Kubernetes 与单体一起使用,就像我们将它与微服务一起使用一样有效。但镜像尺寸会更大,但如果我们使用像 GraalVM 这样的工具,它可能不会大太多。


有了这些,我们就可以跨区域复制整体,并提供与微服务相同的故障转移行为。现在有相当多的开发人员将整体部署到 Lambdas中,我不喜欢这种方法,因为它可能会变得成本非常昂贵,但它确实是有效的。


瓶颈


单体应用一定会遇到的扩展壁垒:数据库。微服务实现了大规模处理,这归功于它们天生就有多个独立的数据库。单体通常使用单个数据库存储,通常它是应用程序的真正瓶颈。有多种方法可以扩展现代数据库,集群和分布式缓存是强大的工具,也可以让我们达到微服务架构中难以匹敌的性能水平。


单体中也不总是单个数据库,在使用 Redis 进行缓存,同时拥有 SQL 数据库并不少见。但我们也可以为时间序列或空间数据使用单独的数据库。也可以使用单独的数据库来提高性能,尽管根据我的经验,这从未发生过。将我们的数据保存在同一个数据库中的优势也是非常大的。


好处


我们可以在不依赖“最终一致性”的情况下完成交易,这是一个惊人的好处。当我们尝试调试和复制分布式系统时,这可能会有一个临时状态,很难在本地复制,甚至很难通过查看可观察性数据来完全理解。


原始性能消除了大量网络开销。通过适当调整二级缓存,我们可以进一步删除 80-90% 的读取 I/O。这在微服务中是可能的,但实现起来要困难得多,而且可能不会消除网络调用的开销。


正如我之前提到的,应用程序的复杂性并没有在微服务架构中消失,人们只是把它搬到了另一个地方。


根据我目前的经验,这并不是一个改进。我们在组合中添加了许多移动部件并增加了整体复杂性,而回归到更智能、更简单的统一架构会更有意义。


为什么使用微服务


编程语言的选择是微服务亲和度的首要指标之一。


微服务的兴起与 Python 和 JavaScript 的兴起相关。这两种语言非常适合小型应用程序,但对于较大的应用并不太好。


Kubernetes 使扩展此类部署变得相对容易,因此它为已经增长的趋势注入了活力。微服务还具有一些相对快速地上升和下降的能力,这样可以更细粒度地控制成本。


在这些方面,微服务作为降低成本的一种方式被出售给公司组织,这并非完全没有价值。如果以前的服务器部署需要强大(昂贵)的服务器,这个论点可能有些道理。对于极端使用的情况,这可能是正确的;突然非常高的负载,然后又没有流量。在这些情况下,可以从托管的 Kubernetes 提供者动态(便宜地)获取资源。


微服务的主要卖点之一是交付方面。它让各个敏捷团队在不完全了解“大局”的情况下解决小问题。问题是,它促成了一种文化,每个团队都做“自己的事”。这在代码腐烂的缩减过程中尤其成问题。系统可能仍然可以工作多年,但实际上已经无法维护。


从 Monolith 开始,为什么要分开?


我的专家小组的一个共识是——人们应该始终从单体开始。它更容易构建,如果我们选择使用微服务,可以稍后再将其分解。但我们为什么要这样做?


与单个软件相关的复杂性拆分为单个模块,而不是单个应用程序会更有意义。资源使用和财务浪费的差异是巨大的。在这个削减成本的时代,为什么人们仍然选择构建微服务,而不是动态的、整体的模块化??


我认为我们可以从两个阵营中学到不少东西。


教条主义一定是有问题的,因为对一种方法的宗教性依恋也是如此。微服务为亚马逊创造了奇迹。但公平地说,他们的云成本已经涵盖在里面了。


另一方面,互联网是建立在单体之上的。它们中的大多数在任何方面都不是模块化的。两者都有普遍适用的技术。我认为正确的选择是构建一个具有适当身份验证基础设施的模块化单体,如果我们想切换到微服务,可以在未来继续利用它。


经Shai Almog 、DZone MVB许可发布。 点击来源查看原始文章。



作者:Shai Almog

译者:场长

来源:https://debugagent.com/is-it-time-to-go-back-to-the-monolith

评论