关于微服务的优势和劣势已经有过太多的讨论,不过我仍然看到很多成长型初创公司对它进行着“盲目崇拜”。冒着“重复发明轮子”的风险(Martin Fowler 已经写过“Microservice Premium”的文章),我想把我的一些想法写下来,在必要的时候可以发给客户,也希望能够帮助人们避免犯下我之前见过的那些错误。
在进行架构或技术选型时,将网络上找到的一些所谓的最佳实践文章作为指南,一旦做出了错误的决定,就要付出惨重的代价。如果能够帮助哪怕一个公司避免犯下这种错误,那么写这篇文章都是值得的。
关于微服务的优势和劣势已经有过太多的讨论,不过我仍然看到很多成长型初创公司对它进行着“盲目崇拜”。冒着“重复发明轮子”的风险(Martin Fowler 已经写过“Microservice Premium”的文章),我想把我的一些想法写下来,在必要的时候可以发给客户,也希望能够帮助人们避免犯下我之前见过的那些错误。在进行架构或技术选型时,将网络上找到的一些所谓的最佳实践文章作为指南,一旦做出了错误的决定,就要付出惨重的代价。如果能够帮助哪怕一个公司避免犯下这种错误,那么写这篇文章都是值得的。
如今微服务是个热门技术,微服务架构一直以来都存在(面向服务架构也算是吧?),但对于我所见过的大部分公司来说,微服务不仅浪费了他们的时间,分散了他们的注意力,而且让事情变得更糟糕。
这听起来似乎很奇怪,因为大部分关于微服务的文章都会肯定微服务的各种好处,比如解耦系统、更好的伸缩性、移除开发团队之间的依赖,等等。如果你的公司有 Uber、Airbnb、Facebook 或 Twitter 那样的规模,那么就不存在什么问题。我曾经帮助一些大型组织转型到微服务架构,包括搭建消息系统和采用一些能够提升伸缩性的技术。不过,对于成长型初创公司来说,很少需要这些技术和微服务。
Russ Miles 在他的《让微服务失效的八种方式》这篇文章中表达了他的首要观点,而在我看来,这些场景却到处可见。成长型初创公司总是想模仿那些大公司的最佳实践,用它们来弥补自身的不足。但是,最佳实践是要视情况而定的。有些东西对于 Facebook 来说是最佳实践,但对于只有不到百人的初创公司来说,它们就不一定也是最佳实践。
如果你的公司比那些大公司小一些,你仍然能够在一定程度上从微服务架构中获得好处。但是,对于成长型初创公司来说,大规模地迁移到微服务是一种过错,而且对技术人来说是不公平的。
为什么选择微服务?
一般来说,成长型初创公司采用微服务架构最主要的目的是要减少或者消除开发团队间的依赖,或者提升系统处理大流量负载的能力(比如伸缩性)。开发人员经常抱怨的问题和常见的症状包括合并冲突、由未完整实现的功能引起的部署错误以及伸缩性问题。接下来让我们逐个说明这些问题。
依赖
在初创公司的早期阶段,开发团队规模不大,使用的技术也很简单。人们在一起工作,不会出现混乱,要实现一些功能也比较快。一切看起来都很美好。
随着公司的不断发展,开发团队也在壮大,代码库也在增长,然后就出现了多个团队在同一个代码库上工作的情况。这些团队的大部分成员都是公司早期的员工。因为初创公司的早期员工一般都是初级开发人员,他们并没有意识到一个问题,那就是在团队规模增长和代码库增长的同时,沟通成本也会随之提升。对于缺乏经验的技术人员来说,他们倾向于通过技术问题来解决人的问题,并希望通过微服务来减少开发团队之间的依赖和耦合。
实际上,他们真正需要做的是通过有效的沟通来解决人的问题。当一个初创公司有多个开发团队时,团队之间需要协调,团队成员需要知道每个人都在做什么,他们需要协作。在这样规模的企业里,软件开发其实具有了社交的性质。如果团队之间缺乏沟通或者缺乏信息分享,不管用不用微服务,一样会存在依赖问题,而就算使用了微服务,也仍然存在负面的技术问题。
将代码模块化作为解决这个问题的技术方案,确实能够缓解软件开发固有的团队依赖问题,但团队间的沟通仍然要随着团队规模的增长而不断改进。
切记不要混淆了解耦和分布式二者的含义。由模块和接口组成的单体可以帮助你达到解耦的目的,而且你也应该这么做。你没有必要把应用程序拆分成分布式的多个独立服务,在模块间定义清晰的接口也能达到解耦的目的。
部分功能实现
微服务里需要用到功能标志(feature flag),微服务开发人员需要熟悉这种技术。特别是在进行快速开发(下面会深入讨论)的时候,你可能需要部署一些功能,这些功能在某些平台上还没有实现,或者前端已经完全实现,但后端还没有,等等。随着公司的发展,部署和运维系统变得越来越自动化和复杂,功能标志变得越来越重要。
水平伸缩
通过部署同一个服务的多个实例来获得系统的伸缩性,这是微服务的优点之一。不过,大多数过早采用微服务的公司却在这些微服务背后使用了同一个存储系统。
也就是说,这些服务具备了伸缩性,但整个应用并不具备伸缩性。如果你正打算使用这样的伸缩方式,那为什么不直接在负载均衡器后面部署多个单体实例呢?你可以以更简单的方式达到相同的目的。再者,水平伸缩应该被作为杀手锏来使用。你首先要关注的应该是如何提升应用程序的性能。一些简单的优化常常能带来数百倍的性能提升,这里也包括如何正确地使用其他服务。例如,我在一篇博文里提到的 Redis 性能诊断(文末有链接)。
我们为微服务做好准备了吗?
在讨论架构选型时,人们经常会忽略这个问题,但其他却是最重要的。高级技术人员在了解了开发人员或业务人员的抱怨或痛点之后,开始在网上找寻找解决方案,他们总是宣称能解决这些问题。但在这些信誓旦旦的观点背后,有很多需要注意的地方。微服务有利也有弊。如果你的企业足够成熟,并且具有一定的技术积累,那么采用微服务所面临的挑战会小很多,并且能够带来更多正面好处。那么怎样才算已经为微服务做好准备了呢?Martin Fowler 在多年前表达了他对微服务先决条件的看法,但是从我的经验来看,大多数成长型初创公司完全忽略了他的观点。Martin 的观点是一个很好的切入点,让我们来逐个说明。
我敢说,大部分成长型初创公司几乎连一个先决条件都无法满足,更不用说满足所有的条件了。如果你的技术团队不具备快速配置、部署和监控能力,那么在迁移到微服务前必须先获得这些能力。接下来让我们更详细地讨论这些先决条件。
1. 快速配置
如果你的开发团队里只有少数几个人可以配置新服务、虚拟环境或其它配套设施,那说明你们还没有为微服务做好准备。你的每个团队里都应该要有几个这样的人,他们具备了配置基础设施和部署服务的能力,而且不需要求助于外部。要注意,光是有一个 DevOps 团队并不意味着你在实施 DevOps,开发人员应该参与管理与应用程序相关的组件,包括基础设施。
类似的,如果你没有灵活的基础设施(易于伸缩并且可以由团队里的不同人员来管理)来支撑当前的架构,那么在迁移到微服务前必须先解决这个问题。你当然可以在裸机上运行微服务,以更低的成本获得出众的性能,但在服务的运维和部署方面也必须具备灵活性。
2. 基本的监控
如果你不曾对你的单体应用进行过性能监控,那么在迁移到微服务时,你的日子会很难过。你需要熟悉系统级别的度量指标(比如 CPU 和内存)、应用级别的度量指标(比如端点的请求延迟或端点的错误)和业务级别的度量指标(比如每秒事务数或每秒收益),这样才可以更好地理解系统的性能。在性能方面,微服务生态系统比单体系统要复杂得多,就更不用提诊断问题的复杂性了。你可以搭建一个监控系统(如 Prometheus),在将单体应用拆分成微服务之前对应用做一些增强,以便进行监控。
3. 快速部署
如果你的单体系统没有一个很好的持续集成流程和部署系统,那么要集成和部署好你的微服务几乎是件不可能的事。想象一下这样的场景:10 个团队和 100 个服务,它们都需要进行手动测试和部署,然后再将这些工作与测试和部署一个单体所需要的工作进行对比。100 个服务会出现多少种问题?而单体系统呢?这些先决条件很好地说明了微服务的复杂性。
Phil Calcado 在 Fowler 的先决条件清单里添加了一些东西,不过我认为它们更像是重要的扩展,而不是真正的先决条件。
如果我们具备了这些先决条件呢?
就算具备了这些条件,仍然需要注意微服务的负面因素,确保微服务能够为你的业务带来真正的帮助。事实上,很多技术人员对微服务中存在的分布式计算谬论视而不见,但为了确保能够成功,这些问题是必须要考虑到的。对于大部分成长型初创公司来说,基于各种原因,他们应该避免使用微服务。
1. 运营成本的增加
快速部署这一先决条件已经涵盖了一部分成本,除此之外,对微服务进行容器化(可能使用 Docker)和使用容器编排系统(比如 Kubernetes)也需要耗费很多成本。Docker 和 Kubernetes 都是很优秀的技术,但是对于大部分成长型初创公司来说,它们都是一种负担。我见过初创公司使用 rsync 作为部署和编排工具,我也见过很多的初创公司陷入运维工具的复杂性泥潭里,他们因此浪费了很多时间,而这些时间本来可以用于为用户开发更多的功能。
2. 你的应用会被拖慢
如果你的单体系统里包含了多个模块,并且在模块间定义了良好的 API,那么 API 之间的交互就几乎没有什么额外开销。但对于微服务来说就不是这么一回事了,因为它们一般运行在不同的机器上,它们之间需要通过网络进行交互。这样会在一定程度上拖慢整个系统。如果一个请求需要多个服务进行同步的交互,那么情况会变得更加糟糕。我曾经工作过的一个公司,他们需要调用将近 10 个服务才能处理完某些请求。处理请求的每一个步骤都需要额外的网络开销和延迟,但实际上,他们可以把这些服务放在单个软件包里,按照不同的模块来区分,或者把它们设计成异步的。这样可以为他们节省大量的基础设施成本。
3. 本地开发变得更加困难
如果你有一个单体应用,后端只有一个数据库,那么在开发过程中,在本地运行这个应用是很容易的。如果你有 100 个服务,并使用了多个数据存储系统,而且它们之间互相依赖,那么本地开发就会变成一个噩梦。即使是 Docker 也无法把你从这种复杂性泥潭中拯救出来。虽然事情原本可以简单一些,不过仍然需要处理依赖问题。理论上说,微服务不存在这些问题,因为微服务被认为是相互独立的。不过,对于成长型初创公司来说,就不是这么一回事了。技术人员一般需要在本地运行所有(或者几乎所有)的服务才能进行新功能的开发和测试。这种复杂性是对资源的巨大浪费。
4. 难以伸缩
对单体系统进行伸缩的最简单方式是在负载均衡器后面部署单体系统的多个实例。在流量增长的情况下,这是一种非常简单的伸缩方式,而且从运维角度来讲,它的复杂性是最低的。你的系统在编排平台(如 Elastic Beanstalk)上运行的时间越长越好,你和你的团队就可以集中精力构建客户需要的东西,而不是忙于解决部署管道问题。使用合适的 CI/CD 系统可以缓解这个问题,但在微服务生态系统里,事情要复杂得多,而且这些复杂性所造成的麻烦已经超过了它们所能带来的好处。
然后呢?
如果你刚好处在一个成长型初创公司里,需要对架构做一些调整,而微服务似乎不能解决你的问题,这个时候应该怎么办?
Fowler 提出的先决条件可以说是技术领域的能力成熟度模型,Fowler 在他的文章里对成熟度模型进行过介绍。如果这种成熟度模型对于公司来说是说得通的,那么我们可以按照 Fowler 提出的先决条件,并使用其他的一些中间步骤为向微服务迁移做好准备。下面的内容引用自 Fowler 的文章。
关键你要认识到,成熟度模型的评估结果并不代表你的当前水平,它们只是在告诉你需要做哪些工作才能朝着改进的目标前进。你当前的水平只是一种中间工作,用于确定下一步该获得什么样的技能。
那么,我们该做出怎样的改进,以及如何达成这些目标?我们需要经过一些简单的步骤,其中前面两步就可以解决很多在向微服务迁移过程中会出现的问题,而且不会带来相关的复杂性。
清理应用程序。确保应用程序具有良好的自动化测试套件,并使用了最新版本的软件包、框架和编程语言。
重构应用程序,把它拆分成多个模块,为模块定义清晰的 API。不要让外部代码直接触及模块内部,所有的交互应该通过模块提供的 API 来进行。
从应用程序中选择一个模块,并把它拆分成独立的应用程序,部署在相同的主机上。你可以从中获得一些好处,而不会带来太多的运维麻烦。不过,你仍然需要解决这两个应用之间的交互问题,虽然它们都部署在同一个主机上。不过你可以无视微服务架构里固有的网络分区问题和分布式系统的可用性问题。
把独立出来的模块移动到不同的主机上。现在,你需要处理跨网络交互问题,不过这样可以让这两个系统之间的耦合降得更低。
如果有可能,可以重构数据存储系统,让另一个主机上的模块负责自己的数据存储。
在我所见过的公司里,如果他们能够完成前面两个步骤就算万事大吉了。如果他们能够完成前面两个步骤,那么剩下的步骤一般不会像他们最初想象的那么重要了。如果你决定在这个过程的某个点上停下来,而系统仍然具有可维护性和比刚开始时更好的状态,那么就再好不过了。
我的总结
我不能说这些想法都是独一无二的,也不能说是我所独有的。我只是从其他遭遇了相同问题的人那里收集想法,并连同观察到的现象在这里作了一次总结。还有其他很多比我更有经验的人也写过这方面的文章,他们剖析地更加深入,比如 Sander Mak 写的有关模块和微服务的文章。不管怎样,对于正在考虑对他们的未来架构做出调整的公司来说,这些经验都是非常重要的。认真地思考每一个问题,确保微服务对你们的组织来说是一个正确的选择。
最起码在完成了上述的前面两个步骤之后,再慎重考虑一下微服务对于你的组织来说是否是正确的方向。你之前的很多问题可能会迎刃而解。
文中部分内容超链接:
http://www.russmiles.com/essais/8-ways-to-lose-at-microservices-adoption
https://aadrake.com/posts/2017-05-15-redis-performance-triage-handbook.html
https://en.wikipedia.org/wiki/Capability_Maturity_Model
https://www.oreilly.com/ideas/modules-vs-microservices
文章来源:https://www.linuxprobe.com/micro-service.html
网友评论