现在许多开发团队已经意识到微服务架构模式会是解决大型单体应用架构所带来的问题的首选解决方案,但是某些团队也反应微服务架构会在某种程度上降低整个开发团队的开发效率。与任何其他架构模式一样,微服务架构也有其利弊之处,结合你特定的应用场景理解这些利弊之处能够帮助你做出更加明智的选择。
微服务带来的好处…
- 高度的模块边界化:微服务促进系统模块化,对大型团队尤其重要。
- 独立部署:简单的服务部署更为容易因为它的依赖更少,并且服务是相对独立的,而且当某个服务出错时不太可能引起整个应用程序系统崩溃。
- 技术多样性:使用微服务你可以混用多种开发语言,开发框架和数据存储技术。
微服务引入的成本…
- 分布式:由于远程调用速度慢且出错风险极高,分布式系统开发难度极大。
- 最终一致性:在分布式系统中维护数据一致性极其困难,它意味着每个服务都必须关注并处理的最终一致性问题。
- 运维复杂性:你需要一个较为成熟的维护团队来管理诸多的微服务,处理日常的微服务更新及频繁部署等问题。
高度的模块边界化
第一个好处是微服务高度的模块边界化低耦合。这是一个重要的好处同时这本身也是一个很奇怪的论断,为什么微服务就应该有高度的模块边界化特性,而单体应用不能有此特性呢,理论上讲没有任何理由这样说。
此处我说的高度的模块边界化是什么意思呢?软件内部某些部分相互依赖高度耦合,好的解决方法是把它们解耦分成不同模块,我相信大家都会认同这种做法。你希望你分解的模块可以在你需要修改系统的某一部分时,只需要理解系统很小的一部分就可以更改,而且要能够很容易就能定位到需要修改的地方。在任何程序中,好的模块化模型都是非常有用的,而且随着软件系统的复杂度渐增其重要程度可能成指数级放大,更为重要的是当开发团队持续壮大的时候。
微服务的提倡者很快就提出了康韦法则,这个概念的意思是软件设计的架构,实际上反应了公司的组织与沟通架构。对于大型团队,尤其是团队分布在不同的地区时,在决定系统架构时,要考虑到小团队内部的沟通会比在大团队内的沟通更为高效。微服务允许各个团队在这种高效的沟通模式下维护自己相对独立的服务单元。
正如我之前提到的,单体应用为什么不应该有模块化特性呢,这说不通,找不到任何理由。但是据大部分人有多年的经验来说,高度模块化的单体应用不过九牛一毛,因为大部分程序任然使用的是“大泥球”这种架构模式。正式由于大部分单体应用最终都会存在这个问题,一些团队开始转向使用微服务架构。模块化之所以可以解耦是因为模块的边界化特性会阻碍模块之间的相互引用,但麻烦的是,对于单体应用系统,通常情况下会绕过这些阻碍。这样做可以帮助开发团队快速交付新功能,是一个高效可行的策略,但是如果大范围使用反而会破坏已有的模块化架构并降低开发团队的工作效率。把模块做成独立分开的微服务会使这种边界化优势更加显著,让那些问题更难出现。
数据持久层是耦合最突出的地方。去数据库中心化是微服务主要的特征之一,也就是说每一个微服务都可以有自己的数据库并自己管理,如果其他服务需要此数据库的数据时,不需要直接集成此数据库而是通过公共的API去获取,这就避免了不必要的数据库集成和耦合,这也是大型系统最严重的耦合之处。
这里也需要强调一下,单体应用也可以定义好的模块边界,但是构建这样的单体应用开发团队需要严格的自制力和毅力。同样微服务也可能被开发成一个“大泥球”系统,但是相对情况下,这样做很难。以我所见,使用微服务能够更好的实现模块化。如果你对团队的自制力和毅力有信心,这种优势可能不太明显,但是当团队不断壮大后维持自制力和毅力就变得愈加困难了,那么为了实现模块化,微服务就变得不可或缺了。
如果你错误的定义了微服务的边界,这种优势可能就变成阻碍。这就是先开发单体应用策略的两个主要原因之一,也就是为什么那些更倾向于提早做微服务的人也必须在理解某个领域之后才能开始做。
然而对于微服务的注意事项还不止这些。只有在一段时间之后你才有可能去评判一个系统模块化程度的好与坏。只有在实践微服务数年之后我们才能够判断微服务是否能够带来更好的模块化特性。此外,早期的实践者或许更加明智,因为大部分能力一般的团队开发微服务之后和从中受益可能会有些延迟。尽管如此,我们也得接受能力一般的软对开发质量一般的软件,与其与顶尖的开发团队比较还不如与使用单体应用架构开发的结果相比——这是一种逆向思维的对比方式。
我现在说的这些都是从早期实践过这种架构的人那里听到的一些真实案例。他们的判断是越早关注模块化越有意义。
有个案例特别有趣。某个团队做了错误的选择了使用微服务架构,但是根据文章微服务代价来说他们的系统还不够复杂。这个项目出现了问题必须挽救,所以大部分人又加入了此项目。这种情况下,微服务架构可以帮上忙,因为比起单体系统来说微服务可以更容易消化大部分开发人员而且可以把大团队平均分配到各个团队。最终,这个团队效率高于开发单体应用,使团队能够追赶进度。但是任然有负面的影响,比起开发单体应用来说,微服务会花费更多的时间开发同样的功能,但是微服务架构在后期也可以提高效率。
有个案例特别有趣。某个团队做了错误的选择了使用微服务架构,但是根据文章微服务代价来说他们的系统还不够复杂。这个项目很快出现了问题需要及时修复,因此一部分新人加入了此项目。对于加新人来说,微服务架构模式可能会起些作用,因为比起单体应用系统来说微服务更容易消化新加入的开发人员,更好的把人员分配到不同的服务团队中。后来,这个团队的开发效率比使用单体应用架构预期的效率要高,使团队能够追赶进度。但是总的来说还是有些影响,使用微服务架构会比使用单体应用架构花费更多的人天,即使这样,微服务架构确实也提高了效率。
分布式
微服务使用分布式系统来提高模块化。但是分布式系统有个很大的缺点,这个缺点正是其“分布式”特性。一旦你选择了分布式架构,你就会面临很多复杂的问题。但是我并不认为微服务社区有考虑到分布式对象所带来的额外成本,但是这种复杂度确实存在。
第一个问题是性能。你不得不正视功能之间调用的性能最近成为了热点问题,但是远程调用速度很慢。如果你的服务调用多个远程服务,而且每一个服务又调用其他服务,所有的响应时间加起来会变成可怕的潜在隐患。
当然你可以通过某种方式缓解这个问题。首先你可以增加被调用服务的粒度,也就是说你只调用一些聚合服务就够了。但这会使编程模型变得复杂,你不得不考虑如何批量的处理服务之间的调用。这种方法只能达到这种地步,因为你至少得一个一个的调用这些聚合服务。
第二种缓解方式是使用异步。如果并行调用六个异步方法,调用速度决定于这六个异步调用中最慢的那个而不是六个异步调用时间总和。这种方式可以获得很大的性能改进,但是同时也会导致另外一个我们所知的问题。异步编程很难:难于实现,也难于调试。但是为了达到可以接受的性能指标我听说过的大部分微服务案例都使用了异步技术。
性能之后是可靠性。你期待正在执行的功能调用能够工作,但是远程方法调用任何时候都可能失败。如果有大量的微服务的话,失败的几率就更大。聪敏的开发人员早就意识到这一点并及早做故障设计。庆幸的是异步调用的策略也同样可以提高故障处理的弹性。但是这并起不了太大作用,你任然需要做其他的工作来找出具体是哪一个远程调用失败了。
这只是分布式计算的谬误提到的前两点。
对于这个问题有一些说明。首先,大部分问题都是随着单体应用不断扩大而导致。某些单体应用都是相对独立的,通常情况下,大部分工作在遗留系统之上。通过网络远程的方式与这些系统交互同样会遇到这些问题。这就是为什么大多数人倾向于更早的使用微服务架构来处理各网络服务之间的信息交付。这种时候经验就很有帮助了,一个有此经验的团队就能更好的解决这些分布式的问题。
但是分布式始终会是个成本问题。每次谈论分布式问题我都有点勉强,因为大多数人在使用分布式机构之前低估了它带来的问题。
最终一致性
我想你也知道访问网站也需要一些时间。你更新某些东西,如果你重新加载页面,想要更新的数据就丢失了。也许等几分钟之后,再次重新加载页面,更新的内容又出现了。
这种实用性很差的问题令人讨厌,这就是关系到最终一致性问题。你的更新操作可能被粉色节点处理,但是你的请求可能被绿色节点处理。直到绿色节点从粉色节点获取更新的结果,你陷入了不一致的情况。最总内容会达成一致,但是在这个过程中你会好奇是不是什么地方出了问题。
微服务由于具有其去数据中心化的特点,使用它就会造成最终一致性问题。对于单体应用来说,你可以在一个事务期间做多件事。而微服务需要更新多个不同的资源,分布式事务是很讨厌的(合理)。开发人员需要重视一致性问题,为了避免后悔,在做任何编码之间,试图搞清楚如何找出哪些过程是不同步的。
单体应用同样具有这些问题。在系统不断变大后,需要更多的做缓存来提高性能,但是缓存失效又是另外一个问题。大多数应用程序都需要离线锁以避免长时间存在的数据库事务。外部系统需要更新但是又不能拥有事务处理。业务流程的不一致性问题没有你想的那么严重,因为业务通常考虑到了这点(业务流程本身就先理解分布式系统CAP原理)。
如其他分布式系统问题一样,单体应用也没办法完全避免不一致的问题,但是这种问题要少得多,尤其是单体应用够小的话。
独立部署
模块边界化和分布式系统的权衡困扰了我整个职业生涯。但是有一件事明显改变,在过去十年里,存在专门负责发布到产品环境的职位。在二十世纪,产品环境发布任然是偶尔而且最痛苦的过程,通常白天黑夜两班倒只是为了把软件放到某个地方可以工作。但是如今,成熟的团队可以频繁的部署的产品环境,许多组织开始实践持续交付,使得他们可以一天多次发布到产品环境。
这种改变对整个产业环境具有重大的意义,同样它也深远的影响着微服务。引入微服务就是为了解决部署大型单体应用的复杂度,即便是修改单体应用很小的一部分也可能导致整个部署失败。微服务的一个主要原则就是服务即组件,它们可以独立的部署。也就是说当你修改某个服务时,你只需要测试然后部署这个服务就够了。即便你搞砸了这个服务,也不会使整个系统崩溃。毕竟,由于需要故障设计,即使你的某个组件完全失败掉也不应当使系统的其他部分停止工作。
这是一种双向选择的关系。由于大部分微服务需要频繁的部署,所以你也许要同时兼顾部署。这就是为什么快速应用部署和快速基础设施创建是微服务的前提。比起这些基础的要求只要,你需要的是做持续交付。
持续交付最大的好处就是减少从想法到产品环境可运行的软件的周期时间。公司组织能够快速反应市场变化,比起他们的竞争对手开发新功能更加快速。
因为使用微服务所以大部分人开始使用持续交付,有必要提一下甚至大型单体应用也可以做持续交付。Facebook和Etsy就是两个最好的例子。也有很多尝试独立部署微服务失败的例子,多个微服务之间需要协调依次发布。但是我也听不少人说微服务做持续交付更为简单,但是我并不是很相信这跟模块化有太多的关系——尽管模块化确实与交付速度直接相关。
运维复杂性
能够快速的部署独立的单元是件好事,但是同时也带来额外的维护难题,半打的应用程序可能变成了上百的微服务,很多公司组织很难找到合适的工具可以处理这种问题。
持续交付的角色就更加突出。对于单体应用来说持续交付是个很有价值的技术,值得尝试去使用它,它也逐渐变成微服务必备的技能。如果在没有自动化和持续交付的能力的话是没有办法处理那么多的服务的。维护复杂性就会随着管理和监控需求的增加而变大。如果微服务同时存在于单体应用程序之中的话,持续交付的成熟度模型就变得有用了。
微服务支持者认为由于每一个微服务都足够小,所以很容易理解。但是危险的是这并不能避免复杂度,它仅仅在不同服务之间转移而已。这就表现为增加运维复杂性,例如在服务之间调试非常困难。好的服务边界可以缓解这个问题,但是错误的服务边界就会使问题变得更糟。
处理这种维护复杂性需要许多新的技能和工具——技能更为重要。现有的工具还不成熟,而且我的本能告诉我即便有好的工具,技能的缺失在微服务的环境里还是起不了多大作用。
然而更好的技能和工具并不是处理微服务带来的维护复杂性最难解决的事情。为了办到这一切你需要引入devops文化:让开发人员,维护人员或者团队中的任何人员加入软件的交付过程。如果你不提高技能和改变文化,你的单体应用可能只是进行缓慢,但是你的微服务蒋会遭受创伤止步不前。
技术架构多样性
由于每一个微服务都是独立的可部署单元,你可以为你的服务选择任意合适的技术栈。微服务可以使用不同的语言,不同的框架甚至不同数据存储介质。允许团队选择合适的工具,针对不同的问题选择合适的语言或框架。
技术多向性的讨论集中体现为具体的事情找合适的工具,但是通常微服务最大的好处是版本控制更为乏味的问题。在单体应用中只需要一个版本,通常导致升级问题。系统的一部可能需要升级某部分新功能但是同时可能破坏系统其他部分已有的功能。处理库版本控制的问题会随着代码库的变大而成指数级放大。
技术多样性是很危险的,它会使开发团队感到恐慌。我知道的大多数开发团队仅推荐使用一小部分核心的技术。这是由于大部分工具比如监控工具限制了你可以需用的技术。
不要低估实践的价值。对于单体应用系统,早期敲定的语言和框架很难改动。十年之后这些决定可能让团队困在这些拙劣的技术之内无法自拔。微服务允许团队尝试新的工具,并且每次迁移一个服务也算是比较先进的技术了。
其他因素
上面列出的只是我能想到的一些主要的利与弊。下面也有些我认为不是很重要的因数。
微服务支持者常说微服务更容易做伸缩性扩展,你可以为你的微服务分配任意需要的负载,而单体应用就不行了。然而这让我想起了过去的经历,实际上有选择性的性能缩放比使用千篇一律的缩放要好得多。
微服务允许你分离一些敏感信息并且还可以在此之上加更多的安全保护。甚至你可以加强微服务之间的安全通信,这样就更难攻破。由于安全问题日益重要,考虑到这一点迁移到微服务是不二之选。即使不这样做,大部分单体应用系统也会创建分离的服务来处理一下敏感信息。
微服务的批判者认为比起单体应用微服务更难于测试。确实很难测试,算是分布式系统复杂性之一——这里介绍一些测试微服务的好方法。对于测试单体应用和微服务的区别来说,更重要的是有自律性的去严肃对待测试。
总结
关于任何架构风格的任何文章都有其局限性,正如我在文章建议的局限性所述。所以读此文章并不能帮助你做任何决定,但是这种文章能够启发你在决定是多考虑一些在文章里提到的因数。每一个提到的优缺点对不同的系统可能有不同的作用,有时候甚至优缺点颠倒(高度的模块边界化可能对复杂的大系统有用,但是对一般的小系统则有害无益),做任何决定都应该基于特定的应用场景。考量哪一个因素才是对你的系统影响最大。此外,我们对微服务架构的经验也相对较少。你只有在系统趋于成熟的时候才能评判架构的正确性,只有在系统开发数年之后才会知道什么样的架构才有用。关于微服务架构,我们还没有多少实际的例子。
单体应用和微服务并不是一个简单的二元选择。它们都是相对比较模糊的概念,这就意味着大部分系统都会处于某个模糊的领域边界内。也有些系统都不属于这两种类型。大部分人,也包括我在内,在讨论微服务时总是拿单体应用作对比,因为对比更为普遍使用的系统更为有意义,但我们必须记住有一些系统式不属于我们提到的这两种类型的。我想单体应用和微服务算是软件架构中最主要的两种。它们都值得被提起,因为它们的有趣特点是很值得讨论的,但是并没有架构师严格的区分它们。
尽管这么说,一个概括性总结微服务代价算是被大家广泛接受:微服务会增加复杂的大系统的生产效率。如果你可以控制单体应用系统的复杂度,你就不应当使用微服务。
但是这些关于微服务的言论并不应该让我们忘记什么才是印象软件项目成功与失败重要的因素。一些软性的因素诸如团队中的人员水平,人员之间的沟通效率,与领域专家的高效沟通,都会对项目有影响,并不决定你是否使用微服务。从技术的角度来说,更多的关注一些诸如整洁代码,好的测试和架构的演进。
脚注
一些人认为“单体应用”是一个贬义词,意味着缺乏模块化结构设计的系统。在微服务的领域里大部分人不会这样做,他们把“单体应用”定义为把整个应用程序开发成一个独立的单元。微服务的支持者普遍认为大多数的单体应用最后都会变成大泥球,但是我不知道会不会有人对构建一个结构化的单体应用是不可能的有异议。
根据微服务的定义微服务可以独立的部署。那也就是说一部分服务必须有自己的部署顺序是反微服务架构的。
延伸阅读
Sam Newman在他的书构建微服务第一章里面介绍了更多微服务的优点。
Benjamin Wootton的博客,微服务-不是免费的午餐最早也是最好的总结了使用微服务的缺点。
鸣谢
Brian Mason, Chris Ford, Rebecca Parsons, Rob Miles, Scott Robinson, Stefan Tilkov, Steven Lowe, and Unmesh Joshi参与讨论和修改。