导读:微服务是当前流行的架构。简单地说,微服务就是一种面向服务的软件架构,在这种架构中,服务器端应用程序是通过组合许多单用途、小容量的网络服务来构建的。微服务架构让边界设计良好的服务的失效互不影响成为可能。但是,微服务和所有的分布式系统一样,也会存各种各样的问题。Shekhar Gulati 给我们分享了微服务失败的 11 个原因,这些原因还算比较常见,我们相信,他总结的心得对于想要尝试转型微服务的公司是大有裨益的,因此,我们翻译并分享了这篇文章,以飨读者。
在过去的几年里,我对进行数字化转型的多家产品团队进行了架构审查。大多数团队都是遵循微服务架构来构建产品。他们完全有理由使用基于微服务的架构:更快的开发、更好的可扩展性、更小的独立团队、独立的部署、使用正确的技术来完成工作,等等。但是,我经常发现,团队在微服务方面举步维艰。他们未能充分利用微服务的优势。在本文中,我将分享我的观点,阐述团队在微服务方面为何举步维艰的原因。
对于刚接触微服务的新手来说,我推荐阅读 Martin Fowler 关于微服务的文章。我很喜欢这篇文章中提到的微服务架构定义。
微服务架构风格就是一种将单个应用程序开发成一套小型服务的方法,每个应用程序都在自己的进程中运行,并与轻量级机制(通常是 HTTP 资源 API)进行通信。这些服务是围绕业务功能而构建的,并且可以由完全自动化的部署机制来独立部署。这些服务只有最低限度的集中管理,可以用不同的编程语言编写,并使用不同的数据存储技术。
我曾与许多非常看好微服务的客户一起合作过。对他们来说,微服务就是解决他们所有问题的灵丹妙药。在与我讨论中,我发现大多数团队及其管理层低估了微服务开发的复杂性。
要开发微服务,开发人员需要一个高效的本地环境设置。
随着系统中的服务数量开始增加,在一台机器上运行应用程序的子集变得越来越困难。特别是当你使用消耗较多内存的语言(如 Java)构建应用程序时,更是如此。
下面是与本地开发设置相关的要点。
docker-compose
文件来提供不同的服务。如果组织对微服务开发的复杂性缺乏理解,那么团队的速度将会随着时间的推移而下降。
在我看来,我发现一个新的平台已经成为一种遗产。团队没有确保依赖项是最新的版本,或者将像数据库这样的工具升级到最新版本。因此,两年前开始的现代化改造,如今已经背负了长达数月的技术债务。
几年前,许多团队开始将 Spring Cloud Netflix OSS 项目用于微服务。他们使用像 Kubernetes 这样的容器编排工具,但是因为他们是从 Netflix OSS 开始的,所以他们没有使用 Kubernetes 提供的所有功能。当 Kubernetes 内置了服务发现时,他们仍然使用 Eureka 作为服务发现。此外,通过类似 Lstio 这样的服务网格,你就可以摆脱 Netflix OSS 提供的大部分服务。这有助于降低复杂性,并将许多“横向关注点”(cross cutting concerns)转移到平台上。
需要记住的另一点是,要使所有服务的依赖项版本保持同步。我最近在帮助一个客户,他使用 Spring Boot 来构建微服务。在过去两年中,他们已经构建了 20 多个 Spring Boot 服务。在他们的环境中,他们使用的 Spring Boot 版本从 1.5 到 2.1 不等。这意味着当有人设置他们的机器时,他们必须下载多个版本的 Spring Boot。此外,他们还缺乏自版本 1.5 以来在 Spring Boot 中所做的许多改进。
我们的建议是,组织应该在其积压中为这些升级创建技术债务项。这些技术债务项应作为架构委员会会议的一部分加以讨论,并定期予以解决。在我的上一个项目中,我们每三个月设置为期一周的 sprint,将所有依赖项更新到最新版本。
由于本地开发状况不佳,大多数团队开始依赖于共享环境来获得关键服务。开发人员机器中的第一件事就是数据库。大多数年轻的开发人员并没有意识到基于共享数据库的开发是“邪恶的”。下面,是我在共享数据库中看到的主要问题:
数据库只是共享服务的一个示例,但它也可以是消息队列、集中缓存(如 Redis)或任何其他服务可以发生改变的服务。
解决这一问题的最好方法是,让开发人员可以轻松地在他们的机器上运行数据库(作为 Docker 容器),并投资创建 SQL 脚本来设置模式和初始主数据。这些 SQL 脚本应该保存在版本控制中,并像维护任何其他代码一样进行维护。
我曾与一个客户进行合作,当时,他们的版本控制系统中有 1000 多个仓库。他们使用的是 Gitlab 版本控制平台。他们有 5 个产品,每个产品都由多个微服务组成。我问他们的第一个问题是帮助我们了解哪些服务及其各自代码库是产品 A 的一部分。他们的首席架构师不得不花一天时间弄清楚所有产品 A 的仓库。一天过去了,她还是不能确定是否弄清楚了所有的服务。
解决这个问题的最好方法是,从一开始就以某种方式对你的微服务进行分组,这样,你就可以随时了解产品的生态系统。Gitlab 提供了一种方法来创建一个组,然后在其中创建项目仓库。Github 并没有组功能,因此你可以使用主题或命名约定来实现它。
我个人更喜欢 mono repos,因为我发现他们真的很方便。我遇到的大多数开发人员都认为它是一种反模式。我同意 Dan Lua 的观点,他提到了 mono repo 的以下好处:
大多数团队并不知道什么应该被视为服务。关于究竟是什么构成一个单一的微服务,人们对此存在很多混淆的认识和困惑的概念。让我们举一个例子,假设你的应用程序具有类似插件的架构,在这个架构中,你集成了多个第三方服务。每个集成应该是一个微服务吗?我看到很多团队,都在为每个集成创建一个微服务。随着集成数量的增加,这种情况很快就会失控,以至于无法管理。这些服务通常太小,以至于将它们作为单独的进程运行,会增加更多的开销。
我认为,哪怕只拥有少量的大型服务,总比提供太多的小型服务要好得多。我将从创建一个服务开始,该服务对业务组织中的整个部门进行建模。这也符合 DDD(领域驱动设计, Domain Driven Design)。我将一个域分为子域和有界上下文。有界上下文表示公司内部的一个部门,如财务部门和营销部门。你可能认为,这会导致大型服务的出现,你是对的。但是,以我的经验来看,将整体重构为微服务总之比反之更容易。随着你的知识经验越来越多,你可以转向代表更小问题的细粒度微服务。你可以应用单一责任原则(Single Responsibility Principle)来了解你的微服务是否变得过大,做的事情是否过多。然后,你可以将它分解成更小的独立服务。任何服务都不应该直接与其他服务的数据库通信。他们应该只通过已发布的合同进行沟通。你可以在 Microservices.io 网站上阅读更多关于按子域模式分解 的内容。
我也遵循了 Backendlore 文档中提到的建议。这个建议可以帮助将服务限制在服务通信上,而服务通信是微服务系统性能低下的首要原因。如果两条信息相互依赖,那么它们应该属于同一个服务器。换句话说,服务的自然边界应该是其数据的自然边界。
我曾经和一个客户合作,该客户在他们所有基于 Java 的微服务复制了四个与特定问题相关的 Java 文件。因此,如果在该代码中发现 bug 的话,就需要将其修复应用到所有地方。我们都知道,在时间紧迫的情况下,我们会错过将更改应用于一个或多个服务。这样会浪费更多的时间,增加挫败感。
这并非说开发团队不懂正确的事情。但是,按照组织结构,人们总是默认采用简单且容易出错的做事方式。
正确的方法是,使用像 Bintray 或 Nexus 这样的工件管理器,并在那里发布依赖项。然后,每个微服务都应该依赖于这个库。你需要构建工具,以便在发布新版本的库时,所有的微服务都应该进行更新和重新部署。
使用微服务并不意味着你不应该使用迄今为止对我们有用的最佳实践。你需要对工具进行投资,使微服务的升级变得更容易,这样人们就不必这样做了。
在没有适合的工具和自动化的情况下,使用微服务会导致灾难。
我发现团队使用多种编程语言、多种数据库、多种缓存,并以最佳工具的名义进行工作。所有这些都在项目的初始阶段起作用,但随着你的产品投入生产,这些选择开始显露出它们的本色。原因就像我们在构建 JavaSpringBoot 应用程序,但是我们意识到 Java 占用了更多的内存,且性能也很差,所以我们决定改用 Node.js。在我上一次任务中,我向团队解释说他们的推理能力很弱。
大多数情况下,这完全取决于上下文。如果你的开发人员还不够成熟的话,那么无论你使用什么编程语言,你开发的都将是糟糕的产品。
我建议一家组织要发布一个团队可以使用的语言列表。我认为 2~3 就是个很不错的数字。另外,要列出一种语言比另一种语言更适合的理由。
在选择一门语言之前,你应该考虑以下一些问题:
这不仅仅局限于编程语言,也适用于数据库领域。如果你的系统中已经有了 MongoDB,那么你为什么要在生态系统中使用 ArangoDB 呢?它们都主要是文档数据库。
要始终考虑使用多种技术的维护和操作方面。
这并非微服务特有的现象,但在微服务生态系统中却变得更加普遍。原因是,大多数团队专注于他们的特定服务,因此他们并不了解完整的生态系统。在我与不同客户的工作中,我发现,只有一群架构师了解整体情况。但是,这些架构师的问题在于,他们并不积极参与日常活动,因此他们对开发的影响力是有限的。
我认为最好的办法是,确保所有团队都有一个架构团队的代表,这样他们就可以使他们的团队与整个架构团队的路线图和目标保持一致了。要成为一个成熟的组织,你需要投资于建立一个轻量级的治理。
在过去几年里,我们接触过的大多数组织都在文档方面遇到了困难。大多数开发人员和架构师要么不去编写文档,要么编写的文档毫无用处。即使他们想写,他们也不知道应该如何记录他们的架构。
我们至少应该记录以下内容:
我建议在版本控制系统中维护所有的文档。
我已经在其他观点中简要地提到了这个原因,但我认为,它值得作为一个顶级原因来提及。微服务要比传统的单体式应用(monolithic application)更为复杂,因为你正在构建一个包含许多活动部件的分布式系统。大多数开发人员还不了解系统的不同故障模式。大多数微服务在构建时都考虑了令人快乐的路径。因此,如果你的管理层只想仅仅关注功能,那么你注定会失败。因为在薄弱平台上构建的功能是无法提供价值的。
组织需要有平台思维。平台思维可不仅仅意味着使用容器和 Kubernetes。它们是解决方案的一部分,但本身并非完整的解决方案。你还需要考虑分布式跟踪、可观察性、混沌测试、函数调用与网络调用、服务间通信的安全服务、可调试性等等。这需要在构建正确的平台和工具团队方面付出认真的努力和投资。
如果你是一家资源有限的初创公司,我的建议是,你要重新考虑微服务战略。了解你所面临的问题是什么。
大多数团队都知道自动化测试对产品的整体质量有多重要,但是他们仍然没有做到。微服务架构为测试地点和测试方式提供了更多的选择。如果你不进行彻底的自动化测试,那么你将会失败得很惨。关于这一点,我不会再赘述,因为网上很多人都写过这方面的内容了。下图是我从微服务测试的文章找到的,这篇文章来自 Martin Fowler 的网站,讨论了基于微服务的系统的测试金字塔。
微服务测试金字塔
作者介绍:
Shekhar Gulati,软件工程师。崇尚自学成才。信奉禁欲主义者的苦修者。
原文链接:
本文最初发表在 Medium 博客,经原作者 Shekhar Gulati 授权,InfoQ 中文站翻译并分享。
领取专属 10元无门槛券
私享最新 技术干货