1.1.2 微服务的取舍
取舍(Trade-off)是软件设计和开发中的主旋律,我们总是需要根据各种各样的限制来做出权衡。在技术选型的时候要取舍,在程序花费更多时间还是花费更多空间上要取舍,在性能和功能上要取舍,甚至给一个函数命名也要考虑许久(你肯定深有体会)。同样,微服务架构在使用上也有取舍之道。本节我们会列举它的几个重要优缺点,以及相应的权衡手段,以帮助各位读者在微服务开发中有的放矢。
1.服务化带来的边界效应
将业务模块拆分成服务后,各部分的边界感会变得更强。因为相比进程内的函数调用来说,跨服务的调用成本更高,你需要考虑通信的问题、延迟的问题,以及如何处理失败调用。而在单体应用中就很容易绕过这种边界限制,如果所使用的开发语言又恰巧没有可见性约束,混乱的调用便会破坏模块化的结构,让它们之间产生耦合。这种耦合的另一个来源是集成的数据库,即不同的模块可以随意访问数据,这在复杂的业务系统中通常是耦合的主要来源。
微服务的去中心化数据分治避免了这一情况的发生。通过服务暴露出的API来访问数据可以让代码更容易表现出清晰的业务意图,同时避免让不同的模块陷入无法分割的泥沼。
服务化带来的边界效应是有好处的,它可以在一定程度上保证业务之间的隔离性。前提是进行服务拆分,即边界定义是合理的。如果在设计时没有把控好正确的边界,这个优点反而会变成缺陷。当你发现你总是需要通过跨服务调用来获取数据时,说明很可能是边界定义有问题。在复杂度上,尽管服务是更小的业务单元,更容易理解,但应用整体的复杂性并没有消除,只不过转移到了服务调用上。调用链的冗长会让这种复杂性体现出来。所以定义好服务边界能减少这类问题。领域驱动设计中的子域、边界上下文可以让我们更好地理解业务的隔离性,为我们定义边界提供帮助。因此,在享受边界效应带来的好处时,要先做好它的定义。
2.独立部署
独立部署能将应用的变更控制在一个范围内,使其他部分不受影响,它还能让演进式开发成为可能。当功能开发完成后,你只需要测试和部署你所负责的服务,哪怕发生了错误,它所影响的也只是一小部分业务,整个系统不会因为你的问题而全部垮掉。当然,前提是具有良好的失败设计。在Kubernetes这样的云原生基础设施的加持下,自动回滚的能力甚至无法让用户知道你其实做了一次失败的部署。
独立部署会加快更新应用的速度。但随着服务的增多,部署行为会频繁发生,应用的迭代速度会和交付速度成正比。因此,具有快速部署能力是保证微服务应用迭代的重要条件。这背后的优势就是能让应用更快地引入新特性,快速响应市场的变化。回想笔者十多年前的软件开发经历,一次产品发布需要经历相当烦琐的流程,痛苦且低效。而现在,在持续交付的加持下,每日构建成了常态化的行为。
但独立部署也会给运维带来更多压力,因为一个应用变成了数十个、数百个微服务。如果没有自动化部署管道,恐怕就没有办法完成大批量服务的更新。同时,管理和监控服务的工作也变多了,相应的操作和工具的复杂性也随之增加。因此,如果你所构建的基础设施和工具还不具有自动化持续交付的能力,微服务并不是你当前的合适选择。
3.技术分治
微服务的相对独立性可以让你自由选择实现它的技术。每个服务都可以使用不同的开发语言,不同的类库,不同的数据存储方案,最终组成一个异构系统。技术分治最大的一个优点就是可以让你根据业务特性选择最合适的开发语言和工具,以便提高开发效率。比如你的服务主要负责大数据分析相关业务,你很可能会优先选择基于Java技术栈的那些成熟的大数据产品。这种自由度还能给开发团队带来自信,让他们有一种自己说了算的主人翁意识。
技术分治的另外一个优点是版本控制。如果你开发过单体应用,相信你很可能有过这样的经历:你使用的依赖库需要升级,但这很可能会影响系统其他的模块,你不得不为此去协调、沟通、等待,甚至最后不了了之。而且随着代码规模的扩大,处理版本问题的难度会呈指数级增长。而微服务完美地解决了这个问题。任何依赖库的升级都只需对自己的服务负责。
技术具有多样性一定是好事吗?当然不是。大多数团队一般都只鼓励使用有限的技术栈,过多引入不同的技术会让团队不知所措:首先是使技术学习成本和维护升级成本增加,另外也让构建一个服务的复杂度增加,特别是使持续集成和持续部署的效率降低。因此,我们的建议是使用够用的技术和工具,并在引入新技术前做好调研和实验,为选型提供参考数据。
4.分布式
分布式能提高系统的模块化程度,但它的缺点也很明显,首先是存在性能问题。相比进程内的函数调用而言,远程调用要慢得多。即便系统没有特别高的性能要求,但如果要在一个业务链上调用多个服务,它们加起来的延迟也是一个不小的数字。如果恰巧传输的数据量又比较大,就会进一步拖慢响应时间。
第二个要面对的问题是上游服务故障。因为网络的介入,远程调用更容易失败,特别是有了大量的服务和调用关系后,故障点会增多。如果设计系统时没考虑面向失败设计,或者不具有很好的监控手段,那么技术人员很容易在出现问题时束手无策,甚至出现负责不同服务的开发人员相互指责、推卸责任的情况,因为在找不到故障点的情况下,谁都不觉得是自己的代码出了问题。这也就是为什么在云原生时代,全链路追踪这样的观察能力越来越受到大家的重视。
第三个缺点是引入了分布式事务。这是非常令人头疼的问题,且目前没有一个完美的解决方案。对于开发人员来讲,要意识到一致性问题,并在自己的系统中开发对应的解决方案。
对于分布式带来的问题,我们需要使用一些方法来缓解。第一个是通过减少调用来降低失败发生的可能性。可以考虑使用批处理的方式来进行调用,即在一次调用过程中完成多个数据请求。第二个方法是使用异步方式。对于没有强依赖关系的服务,不需要直接使用命令方式去调用,通过事件来驱动是更合理的选择。
5.运维复杂性
快速部署大量的微服务增加了运维的困难。如果没有完善的持续部署和自动化手段,这几乎会抵消掉独立部署带来的优势。服务的管理和监控的需求也会增加,操作复杂性也随之增加。为解决这一问题,团队需要全面引入DevOps价值体系,包括实践方法、工具,以及文化。
笔者认为,架构演进的首要驱动力就是降本提效,即提升生产效率、降低生产成本是架构选型的首要考虑因素。本节我们列举了微服务架构中几个需要关注的优缺点,并针对问题提供了相应的解决办法。有一点需要强调的是,微服务带来的生产效率的提升,仅适用于复杂度较高的应用,对于初创团队或者简单的应用场景而言,单体应用几乎是最高效的选择。选择架构需要对团队、技术背景、产品等多方面进行考量后再下结论。
本章后面的部分会介绍云原生应用的相关概念,并为你从传统微服务应用迁移到云原生应用提供参考。