作者 | Tomasz Nurkiewicz
译者 | 李腾辉
策划 | 信远
微服务不能“包治百病”。
时下微服务是一个不错的架构,它具备模块化、可伸缩和高容错这些优点。许多公司都采用微服务架构并取得了巨大的成功,自然而然地,如果你正开始一个新项目,微服务似乎是最佳选择。
然而,大多数采用微服务取得成功的公司并不是一开始就选择了这种架构。以Airbnb和Twitter为例,他们在单体应用过于庞大之后才选择了微服务路线,现在也仍在解决由此带来的复杂性。即使是大公司也仍在寻找使用微服务的最佳方法。所以说,微服务是一把双刃剑,需要权衡利弊。
从单体应用迁移到微服务也绝不是一项简单任务,未经过测验,便采用微服务构建一个新产品则更加复杂。只有在充分评估了替代方案之后,才应该认真考虑是否使用微服务架构。
一、 微服务仅适用于成熟产品
关于从头开始使用微服务,马丁·福勒(Martin Fowler)总结道:
1.几乎所有成功的微服务都是从一个过于庞大而不得不拆分的单体应用开始的。
2.几乎所有从头开始以微服务构建的系统,最后都会因严重的问题而失败。这种情况导致许多人认为,就算你确信你的应用将快速发展壮大,也不应该一开始便采用微服务。
初版设计很难优化得很好,新产品的前几次迭代重点在于寻找用户的真正痛点。因此,成功取决于保持敏捷并能快速优化和重构。在这方面,微服务就比单体应用差得多。如果你没有把握设计好最初的方案,就采用了微服务,那么你的启程之路将更加困难,因为重构微服务比重构单体应用要困难得多。
二、你是否在初创公司或者开发全新项目?
作为一家初创公司,你已经在争分夺秒,在未知的噩耗来临之前努力寻找突破口。此时你不太需要关注扩展性(可能几年之内都不需要),那么为什么要使用复杂的架构而忽视客户的需求呢?
在开发全新项目时也有类似的情况,这些项目不受前期工作的限制,更没有任何决策包袱。《构建微服务:设计细粒度的系统》一书的作者山姆·纽曼(Sam Newman)表示,用微服务构建一个全新的项目非常困难:
我仍然坚信,对现有的旧系统进行划分要比在全新的系统容易得多。你有更多可供帮助的资源,比如你有可供查阅的代码,你可以与使用和维护系统的人员交流讨论,你也知道一个“好”的系统是什么样的——基于当前稳定运作的系统进行改变,让你更容易知道你在哪里做错了,你的决策是否过于激进。
三、微服务不是本地部署的最佳选择
由于所有部件都是动态变化的,微服务部署需要搭配更强大的自动化机制。在常规环境下,我们可以依靠持续部署管道(continuous deployment pipelines)来完成工作——任务开发者部署微服务,消费端尽管使用线上服务就可以了。
然而这并不适用于本地环境,如果开发者发布一个包,需要消费端自行在其本地环境上部署和配置其他的服务,这使得部署变得更具挑战性。
确切的说,开发本地微服务应用也是可行的,正如Semaphore(一个CI/CD平台)也提供了本地化部署模式。然而,在这个过程中我们需要克服几个挑战:
1.本地微服务的版本控制规则需要更加严格,你必须跟踪参与发布的每个单独的微服务。
2.你必须进行完整的集成和端到端测试,因为你无法在生产环境中进行测试。
3.如果不能直接访问生产环境,对微服务应用进行故障排查会困难得多。
四、你的单体应用也许还能用
每个软件都有自己的生命周期。你可能想废弃一个单体应用,因为它很旧并且很复杂。但折腾一个系统也许费力不讨好,如果稍加努力,你也许可以榨出当前系统的更多价值,让他多用几年。
只有在这两种情况下,微服务重构才是不得不做的选择:
1.代码混乱:在不破坏其他功能的情况下,很难在原代码基础上进行更改和添加新功能
2.性能因素:你在扩展单体应用时遇到了瓶颈
五、模块化单体
开发人员想要避免采用单体架构的一个常见原因是,单体更容易变成一坨“代码屎山”。那时很难再添加新功能,因为一切都是相互关联的。
但是单体不一定是一团糟。以Shopify为例:他的代码行数超过300万行,是世界上最大的Rails单体应用之一。但有一点,系统过于庞大会给开发人员带来许多痛苦:
应用非常脆弱,新的代码会产生许多意想不到的影响。作出一些更改可能会引发一连串无关的测试用例失败。例如,计算运费和计算税率复用了一些代码,那么更改计算税率代码的同时可能会影响运费计算的结果。这是高耦合和缺乏边界的结果,也导致测试用例难以编写,并且在CI上运行得非常缓慢。
Shopify没有选择将整个单体应用重写为微服务,而是选择了模块化作为解决方案。
模块化有助于设计更好的单体或者微服务。如果没有认真地定义好模块,我们要么陷入传统的分层式单体(大泥球),或者更差的结果,成了分布式单体应用,它同时具备单体和微服务两者的缺点。
模块化的工作量很大,但它也带来了巨大的价值,使开发可以更加直接。新开发人员在开始变更代码之前不必了解整个应用,一次只需要熟悉一个模块。良好的模块化可以使一个大单体更好上手。
模块化是切换到微服务之前的必要步骤,并且有可能是更好的解决方案。与微服务类似,模块化单体应用通过将代码拆分为一些独立的模块来解决代码耦合的问题。与微服务通过网络进行通信不同,单体应用中的模块通过内部API调用进行通信。
分层式单体对比模块化单体,模块化单体具有微服务的许多特征,却没有微服务面临的诸多挑战。
六、单体应用也能扩展
另一个关于单体应用的误解是它们无法扩展。如果你遇到性能问题并认为微服务是唯一的出路,可以参考Shopify的案例,在音频领域Shopify已经在超大规模上构建了一个可靠的单体应用。
架构和技术栈将决定如何优化单体应用,在做好模块化划分之后,可以利用云原生技术进行扩展:
1.部署单体应用的多个实例,并使用负载均衡器来分配流量
2.使用CDN分发静态资源和前端代码
3.使用缓存来减少数据库负载
4.使用边缘计算(edge computing)或者无服务调用(serverless function)来实现高需求功能
七、如果系统可高效工作,不要轻易尝试改变
如果我们将生产力衡量标准定义为每时间单位实现了多少个有价值的功能,那么在生产力值很高时,切换架构几乎没有意义。
由于维护开销较大,微服务最初是生产力较低的架构,随着单体的增长,系统变得更加复杂,并且更难添加新功能。微服务只有在交叉点之后才会获得更高的生产力。
诚然,有些事情最终还是要做,但那可能是几年后才考虑的事。到那时,需求可能已经发生改变——谁知道那时候是否还会出现新的架构模型呢?
八、布鲁克斯定律和开发人员生产力
在《人月神话(The Mythical Man Month)》一书中,弗雷德里克·布鲁克斯(Frederick P. Brooks, Jr.)曾说:“在软件项目后期增加人力,会让交付时间更晚”。发生这种事是因为必须先对新人员进行指导,然后才能在复杂的代码上进行开发。此外,随着团队的壮大,沟通成本也会增加,使得组织决策更加困难。
在大型软件开发时,布鲁克斯定律指出,在软件项目后期增加人力只会让花费的时间更长。微服务是减少定律影响的一种方法。然而,这种效果只能在复杂而庞大的代码库中才能体现,因为在这种情况下,我们无法将开发划分为各自独立的任务。
在使用微服务之前,你必须考虑布你的单体应用是否正在被鲁克斯定律所影响。过早地切换到微服务不会增加更多的价值。
九、你准备好进行切换了吗?
在开始切换微服务之前,除了准备好你的单体之外,你还必须满足以下条件:
1.为自动化部署设置好持续集成和持续部署(CI/CD)
2.实现快速配置以便按需构建基础架构
3.了解云原生技术栈,包括容器化、K8S、无服务
4.熟悉领域驱动设计(DDD, Domain-Driven Design),测试驱动开发(Test-Driven Development),行为驱动开发(Behavior-Driven Development)
5.团队重组,以便跨职能沟通消除信息孤岛,采用扁平化管理以激发创新
6.培养DevOps文化,使开发人员和运维工作得更加契合
改变组织的文化可能需要数年时间,学习所有的必备知识也许需要数月时间,如果没有做好准备,切换到微服务是注定无法成功的。
十、总结
我们可以用一句话总结上面关于切换到微服务的讨论:除非你有充分的理由,否则不要轻易去做。那些毫无准备、没有可靠设计就使用微服务的公司,都将经历一段非常艰难的时期。你需要建设好技术文化氛围,做好技术储备,再去考虑微服务。
同时,如果你的系统运行良好并且仍在以预期的速度进行开发,那么为什么要急于改变呢?
最后感谢你阅读本文,祝你编码愉快!
原文链接:
https://dzone.com/articles/when-microservices-are-a-bad-idea
译者介绍
李腾辉,51CTO社区编辑,目前在一家东南亚互联网金融独角兽担任资深Java工程师,负责金融借贷平台架构设计及核心建设工作,对互联网金融架构、微服务体系有较深入的研究,期望在互金领域持续深耕。