一、架构的发展历程
我坚定的认为要深刻的理解一项技术光靠网上一两张按照各项维度对比的表格是不够的,而是要了解这些技术出现的历史背景:他们的出现到底是解决了什么问题,又带来了什么新的问题,最后又因何而被淘汰。下面这部分内容参考《凤凰架构》以及Martin Fowle等人一些文章进行整理,一起来看下历史的浪潮是如何推动架构的演进。
1.原始分布式时代
首先介绍的是竟然是“分布式”而不是“单体”这有些反常识,然而事实上分布式确实出现的比单体早,“单体”这个名称是在微服务开始流行之后“事后追认”所形成的概念,在单体出现之前分布式早已流行,并且成果斐然。
1971年Intel公司设计了世界上第一台微型计算机MCS-4,开创了微型计算机的新时代,计算机逐步从专业的科研设备转变为企业的生产设备。但是微型计算机用于商业生产面临一个非常大的问题:计算机硬件有限的运算处理能力,直接限制在单台计算机上信息系统软件能够达到的最大规模。如果你生在这个时代,相信也能自然而然想到这一问题的解决思路:“人多力量大”、”众人拾柴火焰高“,朴素的真理适用于任何地方,当时高校、研究机构、企等不约而同的开始探索“使用多台计算机共同协作来支撑同一套软件系统”的方案,并取得了一系的成果。谈分布式必然绕不开远程调用,所以下面以远程调用为例谈一下这一时期的探索成果有什么局限性,又产生了哪些深渊的影响。
通过多台计算机分布式协同支撑提升系统规模
大家应该了解“UNIX系统的版本战争”这一故事,为了避免相同的“战争”重演,负责制定 UNIX 系统技术标准的开放软件基金会与主流计算机厂商共同制订了DCE/RPC这一影响深远的远程服务调用规范,它也是公认的现代 RPC 鼻祖之一。因为开放软件基金会本身就负责UNIX 系统技术标准的制定,在这个背景下,DCE/RPC带着浓厚的UNIX“简单优先原则”的设计哲学,预设分布式环境中的服务调用、资源访问等操作尽可能透明,使开发人员不必过于关注他们访问的方法或资源是位于本地还是远程。
然而这个过于理想化的目标在当时面临着太多的技术难题,“远程调用”与“本地调用”相比复杂程度完全不可同日而语:服务发现、负载均衡、熔断、隔离、降级、认证、授权等一系列的问题亟待解决。令人敬佩的是面对重重困难,DCE从无到有构建了大量的协议来解决这些问题,真的做到了相对“透明”,但是分布式还有一个致命的问题——网络所带来的性能问题。
我们来推演一下:硬件性能不足——>采用分布式服务——>分布式的远程调用导致性能降低(与解决硬件性能不足的初心相悖)——>通过合并多个请求等方式刻意(开发人员需要意识到自己在写分布式程序,与DCE透明简单相悖)降低网络性能损耗——>人的能力成为软件规模的约束。这时候分布式从结果来看并不成功,设计向性能妥协让简单透明成为一句空话。
当我们玩游戏打BOSS时,喊人解决不了问题还有另外个方法——氪金,20世纪80年代摩尔定律稳定发挥,微型计算机的性能以每两年即增长一倍的惊人速度提升,既然分布式充满了矛盾与妥协,那就加钱换更好的机器,单体时代到来。
通过提升单台计算机性能提升系统规模
2.单体架构时代
在微服务出现之前“单体”都不认为是架构,因为他太“简单”、太“自然”了,以至于我想找到一本关于单体最佳实践的书籍都没有找到。现在很多书籍把单体当做“毒瘤”,甚至会出现微服务比单体先进的观点,然而事实上真的如此吗?
首先,我们先要明确单体是什么:“单体”只是表明系统中主要的过程调用都是进程内通信,不会发生进程间通信,仅此而已。那么进程内调用是罪恶吗?是毒瘤吗?那肯定不是的。现实中不会有人对你说:你这个"Hello, World!"程序不能用单体,因为单体架构是毒瘤。
有个“单体不便于扩展“的论调非常流行,我们着重讨论下这个观点是否准确。我们从性能扩展和功能扩展两个方便说。先说性能扩展:这太简单了,把一个服务部署多个副本,前面加一个负载均衡服务器来均分流量,这是不是就实现了扩展?再说功能扩展:纵向上我从来没见到哪个大型系统代码是不分层的,用过Spring都知道写一个服务基本上默认按照MVC模式分层以便于扩展;而横向上我们也可以从功能等维度拆分为不同模块(模块之间不进行通信或者进行少量的通信,注意:拆分模块不代表不是单体服务,单体服务也不表示系统只有一个模块),各模块完全可以独立迭代。从这个角度来看单体服务在“可扩展”这一点上也不输与微服务,甚至在开发、调试等方面更优。当前单体服务也有其局限性,比如有个C++实现的系统要实现部分AI功能,明显Python更有优势,C++中执行Python虽然可以实现,但是显然不如微服务独立一个Python模块来的优雅。现在回来看“单体服务不便于扩展”这个观点并不完全正确。
“罪恶的单体”是“大型的单体系统”,而“大单体服务”的主要罪恶在于隔离性。举一个例子,不小心写了一个BUG会导致程序崩溃,如果是微服务只会导致一个模块崩溃,影响的范围也仅仅是依赖这个模块的功能,可是如果是单体服务,所有代码都共享着同一个进程空间,某一处崩溃直接导致整个进程崩溃,那影响的范围就大了。到这里我们可以简单的说:单体服务在同一进程更简单、高效,其代价是损失了隔离能力以及技术扩展性。至于这两点谁轻谁重不可一概而论,要看你的系统到底是一个小卖店还是一个大超市了。
既然单体和微服务各有优劣,为什么后来微服务潮流滚滚而来?互联网是不断发展的,随着微型计算机的普及,软件系统的功能越来越多,对应的开发团队规模也越来越大,我们逐步进入了“大兵团作战”时代,这时候“墨菲定律”+“黑天鹅事件”对系统可用性影响越来越大。
构建一个大规模但依然可靠的软件系统非常难。根据墨菲定律可能发生的事(BUG)就一定会发生,再叠加不可预测的“黑天鹅事件”,随着研发团队的规模不断扩大,系统不可用的概率会被无限放大,举个极端点的例子:假如一个公司有10万人,每个人写出系统的可用性都能达到99.999%,可是当他们共同协作写一个单体服务时,那么系统的可用性就是99.999%的10万次方,约等于36.8%,这个系统简直不可用,更何况人是不可靠的,这时候单体服务隔离性差的缺点凸显。微服务把构筑可靠系统观点从“追求尽量不出错”转变为“出错是必然”,通过拆分服务以减少异常的影响范围,关于微服务为什么可以提升系统的可用性可以用一个例子帮助理解:人体系统由一个个的不可靠的细胞(微服务)组成,大多数情况下一个或者一群微服务(细胞)的崩溃并不会影响我们的生命,我们的人体系统就是用不可靠部件构造的可靠的系统例子。