微服务失败的 11 个原因

22-12-27 banq

在过去的几年里,我对多个正在进行数字化转型的产品团队进行了架构审查:大多数团队都按照微服务架构构建产品,他们使用基于微服务的架构的意图是正确的——更快的开发、更好的可扩展性、更小的独立团队、独立部署、使用正确的技术来完成工作等等。
但是,大多数时候我发现团队在微服务上苦苦挣扎。他们未能充分利用微服务的优势。
在这篇文章中,我将分享我认为团队在微服务方面苦苦挣扎的原因。

原因1:管理层低估了开发微服务的复杂性
我曾与多个非常看好微服务的客户合作。对他们来说,微服务是解决他们所有问题的银弹解决方案。在我的讨论中,我发现大多数团队和他们的管理层都低估了微服务开发的复杂性。

为了开发微服务,开发人员需要一个高效的本地环境设置。

随着系统中的服务开始增加,在一台机器上运行应用程序的子集变得很困难。当你用Java等消耗相对较多内存的语言构建应用程序时,这种情况尤其会发生。

以下是与本地开发设置有关的要点:

  • 本地开发的第一个重要方面是一个好的开发机器。我觉得很难,大多数组织都想使用所有最新和最伟大的技术,但他们不想取代可怜的Windows开发机器。开发人员受限于他们的开发机器。我见过开发者使用配置不佳的机器来构建基于微服务的系统。这降低了他们的生产力,他们不能完全应用自己。使用糟糕的开发机器的一个副作用是,开发人员不能得到快速的反馈。如果你知道你必须等待几分钟才能运行你的集成测试套件,那么你宁愿不写更多的测试套件,也要增加你的痛苦。糟糕的开发机器会促进糟糕的开发实践。
  • 一旦你有了你的开发人员合理的机器来工作。接下来就是确保所有的服务都使用一个构建工具。你应该能够在一台新的机器上构建整个应用程序,而不需要太多的配置。根据我的经验,即使是微服务,使用一个可以构建整个应用的根构建脚本也是有帮助的。
  • 下一个要点是使开发人员能够在他们的系统上轻松地运行应用程序的一部分。你应该使用多个docker-compose文件来启动不同的服务,并配置好所有的端口和体积。
  • 接下来,如果你使用的是像Kubernetes这样的容器编排工具,那么你应该投资像Telepresence这样的工具,让你在Kubernetes集群中轻松调试应用程序。


如果一个组织不了解微服务开发的复杂性,那么随着时间的推移,团队的速度会下降。


原因2:没有更新库和工具到最新版本的过程
在我的审查中,我发现一个新的平台已经成为一种遗产。团队没有确保依赖关系是最新的,或者数据库等工具是最新的版本。因此,两年前开始的现代化努力,今天已经有了几个月的技术债务。

几年前,许多团队开始使用Spring Cloud Netflix OSS项目进行微服务。他们正在使用像Kubernetes这样的容器编排工具,但因为他们从Netflix OSS开始,他们没有使用Kubernetes提供的所有功能。他们仍在使用Eureka作为服务发现,而Kubernetes已经内置了服务发现功能。另外,通过Istio这样的服务网,你可以摆脱Netflix OSS提供的大部分功能。这有助于降低复杂性,并将很多交叉性的问题转移到平台上。

另一点要记住的是,要保持所有服务的依赖版本同步。我最近在帮助一个使用Spring Boot构建微服务的客户。在过去的两年里,他们已经建立了20多个Spring Boot服务。在他们的环境中,他们使用的Spring Boot版本从1.5到2.1不等。这意味着当有人设置他们的机器时,他们必须下载多个版本的Spring Boot。此外,他们还错过了Spring Boot自1.5以来的许多改进。

我们的建议是,组织应在其积压中为这些升级创建技术债务项目。这些技术债务项目应该作为架构委员会会议的一部分来讨论,并定期解决。在我的上一个项目中,我们每三个月设置一个星期的冲刺,将所有的依赖关系更新到最新版本。

另外,团队应该投入时间将数据库、消息队列和缓存等工具升级到最新版本。

原因3:使用共享服务进行本地开发
由于本地开发不力,大多数团队开始依靠共享环境提供关键服务。首先走出开发者机器的是数据库。大多数年轻的开发者并没有意识到基于共享数据库的开发是邪恶的。以下是我看到的共享数据库的主要问题:

  1. 团队成员必须建立一个工作的社会契约,以避免最后一个作者获胜的问题。一个开发者可以抹去另一个开发者为其工作所写的数据。这种工作方式是痛苦的,而且容易失败。这迟早会对团队造成伤害。
  2. 开发人员害怕实验,因为他们的工作会影响其他一些团队成员。我们都知道,学习的更好方式是实验和快速反馈。有了共享数据库,实验就出了门。我们需要实验来提出数据库模式,并完成性能调整等任务。
  3. 另一个副作用是,在隔离的情况下测试变化变得很困难。你的集成测试会变得不稳定。因此,进一步降低了开发速度。
  4. 共享数据库必须像宠物一样被对待,因为你不希望它有一个不一致的和不可预测的状态。你可能有一个开发人员想测试一个表为空时的边缘情况,但其他人需要一个表有记录。
  5. 只有共享数据库才有系统工作所需的所有数据。团队成员随着时间的推移失去了变化的可追溯性,所以没有人知道他们如何在自己的机器上复制相同的设置。唯一的办法是采取完整的数据库转储,然后用它来工作。
  6. 当你没有连接到网络时很难工作。这通常发生在你有一个很长的通勤时间或你在飞行的时候。

数据库只是共享服务的一个例子,但它也可以是消息队列、集中式缓存(如Redis)或任何其他服务可以变异的服务。

解决这个问题的最好方法是让开发人员能够很容易地在他们的机器上运行数据库(作为docker容器),并投资于创建SQL脚本来设置模式和初始主数据。这些SQL脚本应该被保存在版本控制中,就像其他的代码一样被维护。


原因4:在他们的版本控制托管平台中缺乏可见性
我曾与一个客户合作,他们的版本控制系统中有超过1000个存储库。他们正在使用Gitlab版本控制平台。他们有5个产品,每个产品都是由多个微服务组成的。我问他们的第一个问题是帮助我们了解哪些服务和它们各自的代码库是产品A的一部分。他们的首席架构师不得不花一天时间来弄清楚构成产品A的所有代码库。

解决这个问题的最好方法是,从一开始就以某种方式将你的微服务分组,这样你就能始终对你的产品生态系统保持关注。Gitlab提供了一种方法来创建一个组,然后在里面创建项目库。Github没有组的功能,所以你可以使用主题或命名规则来实现它。

我个人更喜欢mono单体仓库,因为我觉得它们非常方便。我遇到的大多数开发者都认为这是一种反模式的做法。我同意Dan Lua的帖子,他提到了mono repo的以下好处

  • * 简化组织
  • * 简化依赖关系
  • * 工具化
  • * 跨项目变化



原因5:对服务没有明确的定义
大多数团队都不知道什么应该被认为是服务。围绕着什么是真正意义上的单一微服务,存在着很多混乱和虚假。让我们举个例子,你的应用程序有一个类似插件的架构,你将与多个第三方服务集成。每个集成都应该是一个微服务吗?我已经看到多个团队在为每个集成创建一个微服务。随着集成数量的增加,这很快就变得难以管理了。这些服务通常都太小了,以至于它们作为一个单独的过程运行,会增加更多的开销。

我认为拥有少数大型服务比拥有太多小型服务要好。我将从创建一个服务开始,该服务为企业组织中的整个部门建模。这也是符合DDD的。我把一个域分成子域和有界上下文。一个有边界的上下文代表了一个公司中的一个部门,如财务和营销。你可能会认为这可能会导致大型的微服务,你会是对的。但是,根据我的经验,将单体重构为微服务总是比反过来做要容易。随着你获得更多的知识,你可以向代表较小关注点的细粒度微服务发展。你可以应用单一责任原则来了解你的微服务是否变得太大,做了太多事情。然后你可以把它分解成更小的独立服务。任何服务都不应该直接与另一个服务的数据库对话。它们只应该通过发布的合同进行通信。你可以阅读更多关于Microservices.io网站上提到的通过子域模式分解的内容。

我也遵循backendlore文档中提到的建议。这个建议可以帮助限制服务与服务之间的通信,这是基于微服务的系统中性能低下的首要原因。

如果两个信息是相互依赖的,它们应该属于一个服务器。换句话说,一个服务的自然边界应该是其数据的自然边界。


原因6:没有明确的代码重用策略
我曾与一个客户合作,该客户在他们所有基于Java的微服务中复制了四个与特定问题有关的Java文件。因此,如果在该代码中发现了一个错误,它需要在所有地方应用。我们都知道在时间压力下,我们会错过将我们的改变应用到一个或多个服务中。这将浪费更多的时间并增加挫败感。

这并不是说开发团队不知道正确的事情。但是,组织的结构方式使人们总是默认简单和容易出错的做事方式。

正确的方法是使用一个工件管理器,如Bintray或Nexus,并在那里发布依赖关系。然后,每个微服务都应该依赖这个库。你需要建立工具,以便当库的新版本发布时,所有的微服务都应该被更新和重新部署。

使用微服务并不意味着你不应该使用到目前为止对我们有效的最佳实践。你需要投资于工具,使升级微服务变得容易,这样就不必由人去做了。

在没有适当的工具和自动化的情况下使用微服务是一个灾难的秘诀。


原因7:多语言编程
我发现有的团队以最佳工具的名义使用多种编程语言、多种数据库、多种缓存来完成工作。这在项目的初始阶段是可行的,但当你的产品进入生产阶段时,这些选择就开始显示出其真正的色彩。原因是,我们正在构建Java Spring Boot应用程序,但我们意识到Java消耗更多的内存,而且性能很差,所以我们决定切换到Node.js。在我的最后一项任务中,我向团队解释说他们的推理很弱:

  • Node.js的性能比Java好。如果你有基于IO的工作负载,Node.js通常表现得更好。在任何计算密集型工作负载上,Java都能击败node.js。通过使用反应式范式,你可以用Java为IO工作负载提供更好的性能。Spring Boot Reactor的性能与Node.js的IO工作负载相当。
  • Node.js消耗的内存比Java少。这是部分事实,因为Node.js应用程序通常比Java使用更少的内存。Java Spring Boot应用程序并不像大多数人认为的那样糟糕。我对Spring Boot的一个Java微服务进行了负载测试,内存消耗仍然低于1GB。你可以通过OpenJ9 JVM、限制类路径上的依赖关系,以及调整默认的JVM参数来优化Java的内存利用率。此外,还有一些新的Java中的Spring Boot替代品,如Micronaut和Quarkus,它们的内存消耗与Node.js相当。
  • Node.js比Java更有生产力。这取决于开发人员编写代码。具有静态类型和静态分析工具的Java可以帮助在开发生命周期的早期发现问题。
  • 大多数时候,这一切都取决于环境。如果你的开发人员缺乏成熟度,无论你使用何种编程语言,你都会开发出糟糕的产品。


我建议一个组织公布一个团队可以使用的语言列表。我认为2-3种是个好数字。同时,列出为什么要使用一种语言而不是其他语言的理由。

在选择一种语言之前,你应该考虑多种原因。
  • 要找到成熟的企业软件开发人员有多容易?
  • 对开发人员进行新技术的再培训有多容易?我们发现Java开发人员可以相对容易地学习Golang。
  • 初始团队之外的开发者有多容易贡献、转移和维护他人编写的代码?
  • 在工具和库方面,生态系统的成熟度如何?

这不仅限于编程语言。这也适用于数据库。如果你的系统中已经有了MongoDB,那么你为什么要在你的生态系统中使用ArangoDB?它们都是以文档数据库为主。

始终要考虑到使用多种技术的维护和运维操作方面的问题。


原因8:人员依赖性
这并不是微服务所特有的,但在微服务生态系统中,它变得更加猖獗。原因是大多数团队都专注于他们特定的服务,所以他们不了解完整的生态系统。在我与不同客户的合作中,我发现只有一些架构师了解整体情况。但是,这些架构师的问题是,他们在日常活动中并不活跃,所以他们对开发的影响是有限的。

我认为最好的办法是确保所有的团队都有一个架构组的代表部分,这样他们就能使自己的团队与整个架构组的路线图和目标保持一致。为了成为一个成熟的组织,你需要投资建立一个轻量级的治理。


原因9:缺乏文档
在过去的几年里,我们所接触的大多数组织都在为文档而奋斗。大多数开发者和架构师要么不写文档,要么他们写的文档没有用。即使他们想写,他们也不知道应该如何记录他们的架构。

至少我们应该记录以下内容。

  • 设计文档
  • C4模型 中的上下文图和容器图
  • 以架构决策记录的形式跟踪关键的架构决策
  • 开发者入职指南

我建议将所有的文档都保存在版本控制系统中。


原因10:业务功能大于平台的成熟度
我已经在其他观点中简要地提到了这个原因,但我认为它值得作为一个最高级别的原因来提及。微服务比你传统的单体应用更复杂,因为你正在建立一个有许多移动部件的分布式系统。大多数开发人员还没有理解系统的不同故障模式。大多数微服务在构建时都考虑到了一个快乐的路径。所以,如果你的管理层只想关注业务功能完成得早与迟的问题,你就会失败。建立在薄弱平台上的业务功能无法提供价值。

组织需要进入平台心态:平台心态并不仅仅意味着使用容器和Kubernetes。它们是解决方案的一部分,但本身不是完整的解决方案。你需要考虑分布式跟踪、可观察性、混乱测试、函数调用与网络调用、安全的服务到服务的通信、可调试性等。这需要在建立正确的平台和工具团队方面做出认真的努力和投资。

如果你是一个资源有限的创业公司,我的建议是重新思考你的微服务战略。请理解你正在进入什么。

原因11:缺乏自动化测试
大多数团队知道自动化测试在产品的整体质量中是多么关键,但他们仍然没有做到这一点。微服务架构为测试的地点和方式提供了更多选择:Devops。如果你不做彻底的自动化测试,那么你将会严重失败。