微服务的阴暗面
分布式系统很难
更多变动因素
铺天盖地的博客文章、白皮书和幻灯片都在宣扬微服务的优点。它们宣称微服务“提高了敏捷性”、“更具可扩展性”,并承诺一旦你转型,工程师们就会蜂拥而至,争相聘用你。
需要明确的是,尽管微服务的优势有时会被夸大,但在某些情况下,它们的确存在。特别是对于拥有众多团队的大型组织而言,微服务可能非常适用。然而,微服务并非万能——尽管它们有很多优点,但也存在一些显著的缺点。在本文中,我将阐述微服务的分布式特性如何使其本质上更加复杂。
分布式系统很难
分布式系统是指任何一组协同工作以执行任务的计算机。微服务本质上是一种分布式系统,专为提供 Web 服务的后端而设计。
自上世纪70年代分布式系统研究初期以来,我们就知道分布式系统非常复杂。从理论角度来看,其难点主要体现在两个关键领域:共识和部分故障。
共识
从理论角度来看,构建可运行的分布式系统的根本问题归根结底在于共识问题——即对分布式状态达成一致。几乎所有分布式系统研究都试图以某种方式解决这个问题。Paxos、Raft、向量时钟、ACID、最终一致性、MapReduce、Spark、Spanner 以及该领域的大多数其他重要进展,都在以某种方式权衡强共识和性能之间的关系。
为了更好地理解分布式共识的问题,我们举个例子。假设 ABob请求Server_1写入数据,x=5同时 BJill请求Server_2写入数据x=6,那么 A 和 B 哪个x更接近于 A5或 B 6?简单来说,我们可以查看 Ax=5和 B 的写入时间x=6,然后选择最后发生的那个。但是,如何确定写入操作的准确时间呢?看时钟?看谁的时钟?如何确定这个时钟的准确性?如何确定 A、B、C 和 B 的时间一致?众所周知,Bob时钟Jill经常Server_1不Server_2同步,而且(正如爱因斯坦告诉我们的)这是无法解决的[1]。此外,真的需要所有节点都对 A 的值达成一致吗x?如果需要,需要达到怎样的共识程度?达成共识需要多长时间?如果 ABob在尝试达成共识的过程中崩溃了怎么办?问题变得非常复杂。
既然分布式共识难以实现,那么这个问题在微服务架构中又是如何体现的呢?优秀的微服务实现通常会通过直接禁止共享状态来彻底规避这个问题。以上述示例为例,x两个微服务在任何特定时间点都不需要就某个值达成一致x。相反,系统中的所有共享状态都会被转移到外部数据库或容器编排器中。
这种方法既解决了共识问题,又没有解决。说它没有解决问题,是因为从理论角度来看,仍然存在需要管理的共享状态。你只是把它转移了位置而已。顺便说一句,这就是为什么 Kubernetes 和数据库如此复杂的原因。
这种方法确实能解决问题,因为从实际角度来看,Kubernetes 和数据库在管理共享状态方面比大多数微服务做得更好。这些系统是由那些每天都在思考这些问题的工程师设计的。因此,它们更有可能达成正确的共识。
部分失效
考虑一个由单体服务器处理的 HTTP 请求。当收到请求时,单个服务器会从头到尾处理整个事务。如果出现问题,无论是软件漏洞还是硬件故障,整个单体服务器都会崩溃——任何故障都是彻底的失败。
现在考虑一下同一个 HTTP 请求进入一个微服务的情况。这个微服务可能会向其他微服务发送新的请求,而这些微服务又可能会生成更多请求,发送到更多的微服务。假设其中一个微服务发生故障,该怎么办?一个或多个微服务依赖于该微服务正在准备的数据。它们应该怎么做?等待一段时间?等待多久?再次尝试?尝试其他服务?还能找谁?放弃,尽力利用现有数据?微服务必须经过专门设计来处理这些问题,这也使得它们的开发更具挑战性。
有人认为部分故障绝对是一件好事。这种观点认为,通过支持部分故障,应用程序会变得更具弹性——小问题可以巧妙地掩盖过去。但我认为,这种做法的好处微乎其微,在实践中也很少能实现,而且代价是大幅增加了实现的复杂性。
更多变动因素
除了微服务本身的理论挑战之外,它还面临着数量庞大的挑战。如此多的组件使得技术栈的几乎每个部分以及软件开发生命周期的每个环节都变得复杂。
开发方面,
您通常可以直接在笔记本电脑上运行单体应用。但要在本地机器上运行微服务,则需要更专业的工具,例如 docker-compose 和 minikube。此外,微服务会占用大量 CPU 和内存资源,导致在笔记本电脑上运行速度极其缓慢。请注意,您可以查看Kelda,特别是我们的白皮书,其中详细描述了这个问题。
调试方面,
单体架构中的所有操作都在单个进程中完成。您可以选择所需的调试器,然后即可开始调试。而微服务架构中,单个请求可能分布在数十个不同的进程中。虽然像 Jaeger 这样的分布式跟踪工具会有所帮助,但调试仍然是一个挑战。
日志记录:
在单体架构中,您可以将日志存储在文件中,并在需要时获取。而在微服务架构中,您需要像 Splunk 或 ELK Stack 这样的工具来处理日志记录。
监控方面,
像 Nagios 这样的简单服务器端监控工具在拥有数百个微服务时无法扩展。虽然更好的工具(Prometheus/Datadog/Sysdig 等)可以解决这个问题,但仍然很困难。
Chef 和 Puppet 等部署
工具足以部署单体应用,但对于微服务来说,你需要像 Kubernetes 这样更复杂的工具。
单体架构的网络
架构可以用简单的负载均衡器来管理。微服务架构则拥有更多的端点,所有这些端点都需要负载均衡、服务发现、一致的安全策略等等。我想服务网格或许能有所帮助(我对此持保留意见,但这可以留待以后的文章讨论)。
微服务有时确实有意义
从技术角度来看,微服务架构的难度远高于单体架构。然而,从人的角度来看,微服务架构能够显著提升大型组织的效率。它允许大型公司内部的不同团队独立部署软件。这意味着各个团队可以快速推进,无需等待效率最低的团队完成代码质量保证并准备发布。同时,这也意味着大型软件工程组织内部的工程师、团队和部门之间的协调工作量大大减少。
虽然微服务架构很有道理,但关键在于它并非万能灵药。就像计算机科学中的几乎所有事物一样,它需要权衡取舍——在这种情况下,需要在技术复杂性和组织效率之间做出取舍。这是一个合理的选择,但你最好确定自己真的需要这种组织效率,这样才能确保技术上的挑战是值得的。
[1]:当然,地球上大多数时钟的运行速度都远不及光速。此外,一些现代分布式系统(尤其是 Spanner)正是利用了这一点,使用极其精确的原子钟来规避共识问题。然而,这些系统本身也极其复杂,这恰恰证明了我的观点:分布式共识很难达成。