我们已经看到微服务架构可以带来许多好处,但它们也带来了许多复杂性。如果你正在考虑采用微服务架构,那么有能力去衡量其带来的优点和缺点至关重要。实际上,大多数微服务的问题都可以归结于分布式系统,它们在分布式单体应用中和在微服务架构中一样明显。
我们会在本书的其余部分继续深入讨论微服务的痛点。其实,本书大部分内容都是关于如何应对这些痛点所带来的痛苦、煎熬和恐惧的。
随着拥有越来越多的服务,开发者体验可能会开始受到影响。像 JVM 这样的资源密集型运行时会限制在单个开发机器上运行的微服务的数量。我可以在笔记本电脑上运行4个或5个基于 JVM 的独立进程微服务,但我能运行10个或20个吗?可能比较难。即便运行时负担少,你可以在本地运行的服务的数量也还是有限的。如果你无法在一台机器上运行整个系统,就无法继续进行开发工作;而如果你使用了本地无法运行的云服务,那问题将变得更加复杂。
极端的解决方案可能包括引入“云上开发”,即开发人员不在本地开发。我并不喜欢这种方式,因为反馈周期可能会受到很大的影响。相反,我认为限制开发人员必须处理的系统范围可能是一种更直接的方法。但是,如果你想采用更偏向“集体所有权”的模式,虽然任何开发人员都可以在系统的任何部分上工作,但这可能会引发问题。
微服务架构的广泛采用催生了大量新技术,这些技术层出不穷,可能让人感到压力巨大;老实说,其中很多只是近来才被重新包装成“微服务友好型”,它们在处理这类架构的复杂性方面确实能提供合适的帮助。但是,也会有走向另一种危险的可能性——这些新技术可能会导致某种形式的技术崇拜。我见过很多采用微服务架构的公司,它们都认为现在是引入大量新技术(通常是外来技术)的最佳时机。
微服务可以让你选择使用不同的编程语言来编写每个微服务、在不同的运行时上运行或者使用不同的数据库,但这些只是可选项,而不是必选项。你必须仔细权衡所采用的技术广度和复杂度,以及它们可能带来的成本。
在开始采用微服务时,你必然会面对一些基本的挑战:需要花费大量时间来理解有关数据一致性、延迟、服务建模等方面的问题。如果在试图搞清楚这些问题会对目前的开发过程有何影响的同时,还要运用大量新技术,那么这会让工作难上加难。同样值得指出的是,学习新技术将会占用原本可以用来向用户交付功能的时间。
随着架构的复杂性逐渐增加,你会根据需要引入新技术。当你拥有3个服务时,你并不需要 Kubernetes 集群。除了确保你不会因这些新工具的复杂性而不堪重负,这种按需增加的好处还在于,你能够获得新的、更好的做事方式,这些方式会随着时间的推移而逐渐成形。
至少在短期内,你很有可能会看到多种因素导致的成本增加。首先,你可能需要运行更多额外的东西——更多的进程、计算机、网络、存储空间以及更多的支持软件(这将产生额外的许可费用)。
其次,向团队或组织引入的任何新的变化都会在短期内减慢交付速度。学习新想法并搞清楚如何有效地利用它们是需要时间的。同时,其他活动也将受到影响。这将直接导致新功能的交付放缓,或者需要增加更多的人手来抵消这种代价。
根据我的经验,对于一个重点关注降低成本的组织来说,微服务并不是一个好选择,因为这类组织将 IT 视为成本中心而不是利润中心的成本控制思路会让你无法充分利用这种架构并从中受益。但从另一个角度来看,如果你可以使用这些架构来触达更多客户或能够并行开发更多功能,那么微服务将有助于赚更多的钱。所以,微服务是一种增加利润的方式吗?有可能。微服务是一种降低成本的方法吗?不一定。
单体系统通常有一个单体数据库。这意味着想要一起分析所有数据(通常涉及跨表的大型连接操作)的有关各方有一个现成的结构(schema )来生成他们的报表。报表可以直接在单体数据库上生成,或者可以采用只读副本,如图1-12所示。
图1-12:直接在单体数据库上生成报表
采用微服务架构意味着我们打破了这种单体系统模式。这并不是说我们不再需要跨模块一起分析所有数据,而是工作变难了,因为我们的数据现在分散在多个逻辑隔离的结构中。
更现代的生成报表的方法,例如用流式传输来为大量数据生成实时报表可以很好地与微服务架构配合使用,但通常这需要采用新思路、使用新技术。或者,你只需将微服务中的数据发布到中心数据库(或结构化较低的数据湖),以满足生成报表的使用场景。
在标准的单体应用中,我们可以用比较简单的方法实现监控。涉及的机器数量一般不多,服务的故障模式无非有两种——要么服务全部正常运行,要么服务全部无法运行。在微服务架构中,如果有一个服务实例出现故障,我们是否了解这个故障会产生的影响?
对于单体系统,如果 CPU 占用率长时间卡在100%,这会是一个大问题,而在有数十或数百个进程的微服务架构中,这是一个大问题吗?在微服务架构中,当只有一个进程的 CPU 占用率达到100% 时,我们还需要在凌晨3点叫醒某人尽快处理吗?
幸运的是,现在这一领域有很多有帮助的观点。如果你想进一步探索这个领域,我推荐阅读 Cindy Sridharan 所著的 Distributed Systems Observability (《分布式系统的可观测性》),这是一个不错的起点,尽管本书第10章也会对监控和可观测性进行探讨。
在单进程单体系统中,大部分信息仅在该进程中传输。现在,更多信息是通过服务之间的网络而传输的。这会使传输过程中的数据更容易地被观察到,也更容易遭到中间人攻击而被篡改。这意味着你需要更加注意保护传输中的数据,并确保微服务的接入端点受到保护——只有被授权方才能使用它们。第11章将专门探究这一领域中的挑战。
对于任何类型的自动化功能测试,都需要寻找一个微妙的平衡点。测试所涉及的功能越多(测试范围越广),你对应用的信心就越大。此外,测试的范围越广,就越难设置测试数据和测试基线,且测试的运行时间就会越长;当测试失败时也就越难弄清哪里出了问题。第9章将分享一些在这种更具挑战性的环境中进行测试的技术。
在涵盖的功能方面,端到端测试对于任何类型系统来说,其测试规模都是最大的,且在编写和维护方面要比小范围的单元测试更容易出问题——我们可能已经习惯了这一点。不过,这通常是值得的,因为我们希望通过端到端测试来模拟用户的使用方式以验证系统,从而获得信心。
但是,在微服务架构中,端到端测试的范围变得非常大。现在,我们需要跨多个进程运行测试,且所有这些进程都需要针对测试场景进行部署和配置。我们还需要处理误报,因为环境问题(例如服务实例关闭或部署失败导致的网络超时)也会导致测试失败。
以上因素意味着,随着你的微服务架构的发展,在端到端测试方面,你将获得递减的投资回报。测试成本越来越高,却无法带给你一如既往的信心。这将迫使你采用新的测试形式,比如契约测试或生产环境测试,或者探索渐进式交付技术,比如采用并行运行或金丝雀发布。第8章将会进行探讨。
使用微服务架构时,以前在本地一个处理器上可以完成的处理任务现在被拆分为多个单独的微服务;以前只在单个进程中传输的信息现在需要通过网络进行序列化、传输和反序列化,你可能需要比以往任何时候都更频繁地使用网络。而所有这些都可能导致系统延迟问题愈加严重。
尽管在设计或编码阶段很难准确衡量变更对延迟的影响,但这也是以增量方式进行微服务迁移的重要原因。先做一个小的变更,然后马上衡量其影响——这需要你有某种方法可以测量操作的端到端延迟。像 Jaeger 这样的分布式追踪工具可以在这方面派上用场。但是,你还需要明确这些操作在延迟方面的要求。有时即使操作变慢,只要它仍然足够快,也是完全可以接受的。
从在单个数据库中存储和管理数据的单体系统转变为分布式系统(其中多个进程在不同的数据库中管理状态)会对数据的一致性带来潜在挑战。过去,你可以依赖数据库事务来管理状态变更,但分布式系统很难提供类似的一致性保障。在大多数情况下,在协调状态变更方面,使用分布式事务已被证明问题会很多。
相反,你可能需要开始使用像 Saga(第6章将做详细介绍)和最终一致性等概念来管理和推理系统中的状态。这些理念可能会从根本上改变你对系统数据的思考方式,这在迁移现有系统时可能会让人望而生畏。同样,这也是在拆解应用系统时需要谨慎小心的另一个理由。采用增量方式进行拆分可以让你及时评估架构变更在生产环境中产生的影响,这一点非常重要。