不要从微服务开始!单体是你的朋友? - arnoldgalovics

21-12-17 banq

我想写下为什么从一个全新的项目开始使用微服务通常是一个坏主意。

时机已到,这正是我将在本文中讨论的内容。

微服务变得越来越自然,我们几乎感觉自己一直生活在微服务的世界里。最近,当我与其他开发人员交谈并询问他们将如何启动绿地项目时,几乎可以肯定,答案是,嗯,一个微服务用于此,另一个用于此,另一个用于用户管理,另一个用于身份验证,另一个用于授权,还有一个用于会话管理,列表可以继续。

我将尝试阐明没有人在谈论微服务时的内容。是的,这将是我过去参与的项目的第一手经验。

 

谎言

我收集了其他文章提到的微服务的一些优点:

  • 故障隔离:

    由于应用程序由多个服务组成,如果一个服务出现故障或出现问题,只会影响系统的那一部分。想想 Netflix,当你在看节目时,你不在乎推荐。所以如果他们有一个服务来处理当前的观看者并为他们提供视频流;他们还有另一项服务来处理个人用户推荐。如果推荐服务宕机,他们系统中最重要的功能不会受到影响,即观看节目。故障被隔离。

  • 消除技术锁:

    想想单体:这是一个巨大的应用程序,拥有成百上千个 API,正在管理数百个数据库表。该应用程序是用 Java 编写的,该团队花了过去 5 年的时间来开发它。一种奇特的新语言出现了,它在纸面上带来了更好的性能,提供了更好的安全性,等等。这可能是 Go/Rust,团队想要试验该语言及其技术堆栈。他们如何在单体环境中做到这一点?他们不能,因为它是单个部署包。您可以 - 至少不容易 - 将应用程序的某些部分切换为不同的语言。对于微服务,您可以为不同的服务使用不同的技术堆栈。服务 A 可以用 Java 编写,服务 B 可以用 Go 编写,服务 C 可以用Whitespace编写,如果你够勇敢的话。

  • 更容易理解:

    当您有多个服务负责整体功能的一小部分时,服务本质上会更小,因此更容易理解。

  • 更快的部署:

    在常规的单体系统中,您要么完全部署,要么根本不部署。您需要部署一个包,这是一个全有或全无的方案。使用微服务,您有机会独立部署,这意味着如果您需要部署推荐服务的升级(回到 Netflix 示例),您可以完全部署该单个服务并节省大量时间。

  • 扩展性

    您可以通过启动多个实例来扩展您的服务,以增加特定功能的容量。就像前面的例子一样,如果人们在 Netflix 上查看大量推荐,他们可以轻松启动推荐服务的多个实例来应对负载。在单体环境中,您要么向上扩展应用程序的每个部分,要么不扩展。

    现实生活中的微服务

 

现实生活中的微服务

  • 基础设施要求

让我从我在微服务方面遇到的最大困难之一开始:基础设施

你有没有部署过单体应用?当然,我们可以将其复杂化,但在常规情况下,如果您将其部署到云中,这就是单体应用所需要的。让我们以一个简单的在线商店应用程序为例。

  • 应用程序的负载均衡器
  • 用于运行应用程序的计算实例
  • 应用程序的(关系)数据库
  • Jenkins for CI(CD)
  • 用于日志聚合的 Kibana

如果您要使用微服务:

  • Kubernetes 集群
  • 负载均衡器
  • 用于运行应用程序和托管 K8S 集群的多个计算实例
  • 一个或多个(关系)数据库,取决于您是否要为每个服务使用一个数据库
  • 用于服务-服务通信的消息系统,例如 Kafka
  • Jenkins for CI(CD)
  • 用于日志聚合的 Kibana
  • 用于监控的 Prometheus
  • 用于分布式追踪的 Jaeger/Zipkin

这只是一个高层次的概述。这应该是相当清楚的。微服务确实可以带来价值,但问题是;以什么代价?

尽管承诺听起来非常好,但您的架构中有更多的移动部分,这自然会导致更多的失败。如果您的消息系统出现故障怎么办?如果您的 K8S 集群出现问题怎么办?如果 Jaeger 宕机而您无法追踪错误怎么办?如果度量没有进入 Prometheus 怎么办?

最初,您将花费更多时间(和金钱)来构建和操作这个复杂的系统。

(banq注:这是从上帝视角看复杂,这些是Complicate繁杂的,但是可以解决,因为每个团队分别负责构建和维护这个Complex系统,否则单体就只是一个Complex复杂性系统,试图依靠几个人构建和维护一个单体复杂性系统,表面上OK,实际是自欺欺人,微服务将Complex复杂性变成Complicated系统,作者可能没有上升到这种认知,以下基本都是从这种上帝视角的认知级别看问题,所以得出单体比微服务好的结论)

 

更快的部署?

我将触及优势列表中的第一点;更快的部署。当你想到 Netflix、Facebook、Twitter 并观看他们的会议演讲时,他们描述了他们正在运行的微服务的数量,以及他们如何向 Git 提交一些东西,并且在几个小时内它就会投入生产。是不是好得令人难以置信?

在我看来这绝对是可以实现的,但我承认我从未参与过这样的微服务项目。我并不是说这是不可能的,从稳定性、基础设施和文化的角度来看,这真的很难做到。

让我分享一下这通常是如何从我的经验中得出的。在对绿地项目进行一行编码之前,您通常会先进行一些探索,即如何将产品转化为技术解决方案。你设计系统,你设计你的微服务,有多少人会在那里,责任等等。

一个真正的教育项目是我们进行这项练习的地方,我们最终在 4 个月内完成了 80 多个微服务?

这 80 多个微服务在现实中的含义是,与将 80 多个微服务放在一个整体中并进行部署相比,我们绝对可以更快地部署单个服务,但是……

80 多个微服务太小了,以至于一个单独的开发单元——敏捷世界中的故事——永远无法只涉及一个服务。该系统从根本上被搞砸了,更快部署的承诺立即消失了。我们没有更快的部署,而是相反,更慢的部署。慢得多。

另外,我会多次反思这一点。部署期间更多的移动部件意味着更多的潜在故障。很多时候,基础设施不够稳定,部署随机失败,因为

  • 下载/上传软件包时,Artifactory/Nexus/Docker 存储库在极短的时间内不可用
  • Jenkins builder 随机卡住了

这只是拼图的一部分。产品必须分解为微服务。每个服务都应该对自己的事情负责。例如,Netflix 中的推荐服务应该负责向用户提供推荐。

并非所有东西都是 Netflix,也绝对不是所有东西都那么容易分解成合适的规模并承担合适的责任。这就是 DDD有界上下文可以提供帮助的地方,但一方面,实践起来并不容易,有时甚至没有足够的时间/开放性来处理这些事情。

 

配套文化

无论如何,在我看来,微服务的第二个难点是组织/项目文化。如果产品(部门)不在乎底层系统架构怎么办?我是说他们会吗?

一个例子:如果你有一个包含大量微服务的复杂架构怎么办。产品负责人进来对团队说,让我们开发整个功能。在团队分析了功能请求后,结果发现它将涉及 10-15 个微服务,因为它与许多其他现有功能相关联。在这种情况下你会怎么做?

您尝试将它分解成更小的部分,但它闻起来很可疑,因为该功能没有任何意义,并且会增加很多开销以逐个服务地发布它。您当然不能因为我们使用微服务就对产品负责人说这需要 3-4 倍的时间,对吗?

那次谈话会是什么样子?

  • 产品负责人:大家好,我想出了这个非常棒的功能。我们的竞争对手已经在做,所以我们必须尽快做。有可能在 2 周内完成吗?
  • 团队:嗯,从最初的样子来看,是的,我们可以做到。而且该功能看起来也是带来更多客户的好主意。我们会重新组合并讨论它。
  • 团队:好的,那两周有点小问题。由于我们在做微服务是为了更快,我们需要更多的时间来实现这件事,因为我们必须接触 15 个服务,所以我们需要大约 6 周的时间来进行初始实现。
  • 产品负责人:初始实施?
  • 团队:是的。有 15 项服务对通信至关重要,因此初始实现将不包括错误处理、弹性通信模式、用于调试目的的跟踪和其他整洁的事情。为此,我们需要额外的 4 周。

(banq注:该部分指出复杂性Complicated系统中存在沟通高的成本,这些可以通过团队拓扑 等人员管理来缓解,但是,需要看到进步:微服务已经将系统的复杂性转嫁到管理复杂性,意识到这点是一种进步,你需要有 康威定理 的认知前提。) 

更好的故障隔离

这个自然是真的。如果一项服务出现故障,那么只有该服务会出现故障,对吗?

虽然这有点真实,但它不是非黑即白。

。。。

让我们再举一个例子。用户尝试使用登录服务登录系统。数据加密服务仍然失败,登录服务正在调用分析服务以获取有关在一分钟时间内尝试登录的用户数量和其他一些虚构指标的一些指标。不过,分析服务正在与数据加密服务对话,因为这些数据也需要加密。

现在,编写分析服务的团队匆匆忙忙,没有时间实施正确的错误处理,因此数据加密服务的问题会影响到登录服务。显然,登录服务已经在几个月前完成,并且该服务还没有准备好处理来自分析服务的潜在错误,因此即使非关键分析服务失败,用户登录也会被简单地拒绝。

我知道你的想法。是的,实施登录服务的团队没有为这种情况做好准备是不负责任的,但如果他们认为分析服务会优雅地处理这个问题呢?这写在分析服务的 API 合同中,但它不能那样工作。

那么当你在一个单体应用中会发生什么?服务崩溃在该上下文中并没有真正的意义,但假设由于某种原因连接到数据加密的数据库表不可访问。

在这种情况下,错误处理将很简单,因为您唯一需要准备的就是异常。尽管在过分赞扬单体应用之前,也有不利的一面,如果单体应用倒闭,什么都不起作用。所以这是一个平衡问题,但问问你自己。实现 try-catch 块或处理同步 HTTP 调用/异步消息传递错误是否更容易?

我记得用 80 多个微服务标准化错误处理是一项艰巨的任务,一个团队花了几个月的时间来完成它。这甚至并不意味着在任何地方引入错误处理,而只是将现有错误重写到我们使用的自定义库中,这样我们就可以减少未来错误处理场景所需的繁琐工作。

(banq注:微服务的DevOps运维模式来帮助缓解故障隔离和修复,这也是一种进一步,虽然不如单体+单个团队来得更具有效率,但至少是去中心化的非单点故障修复,单点故障修复需要烧香拜佛。。。。。)

  

我还没说完。我们第 2 章见。

 

1
猜你喜欢