前言
刚开始决定弄懂文中所提到的所有东西,就像我写ByteByteGo呢几篇文章一样,把每一句话都弄懂。但是对于《凤凰架构》来说,这有点太费时间了,并且没有必要,有些东西可能永远都不会用到,但文章为了全面的介绍一个内容,会提到那些东西。所以我还是针对一些自己的疑问,或是想知道的东西进行一些额外备注。
我起初的想法是以博客的形式对文章进行概括,但在这个过程中,我发现很难概括,书中每一句话都有存在的必要,这本书真的非常干货,收获满满。
总结
原始分布式时代:计算机性能严重不足,对分布式进行了初步的探索。
单体系统时代:对于小规模系统来说,易于开发、测试、部署;而对于大规模系统,它最大的问题并不是不可拆分,难以扩展,因为它整体会遵循分层架构的原则,代码也会根据模块、功能划分,以便重用和管理代码,而且也可部署多个类似Jar、War或其它的方式,通过负载均衡来完成扩展的需求。它真正的问题首先是无法阻断错误的传播,任何一部分代码出现错误,都可能继而影响整个程序;二是不便于动态更新,无法做到对某一模块单独停止、单独更新,可维护性差;三是不便于实现技术异构,需要使用同一种语言,甚至同一种架构。促进微服务代替它还有一个最根本的原因,其实是思想的转变,从原来"追求尽量不出错",到"出错是必然的",为了允许出错这是微服务最大的优势。
SOA时代:也叫面向服务架构,基于远程服务调用的方式,也对分布式进行了全面的探索,提出了非常清晰的指导原则,成功解决了分布式环境下的主要问题,而且还提出软件研发的方法论。不仅关注了技术,还关注了研发过程。但是它的缺陷是规范过于严格,复杂性太大,普适性不强,最终没能成功。
微服务时代:通过多个小型服务组合来构建单个应用,特点有像分散治理,技术异构;远程调用;产品化思维,类似敏捷开发,每个人不单单是开发,还会测试、运维等;数据去中心化;强终端弱管道,简化通信机制,使用RESTful风格;容错性和演进式;自动化等,这使得软件的开发变得更自由。缺点是没有了统一的规范和约束,软件的架构设计难度大大提升。
后微服务时代:在微服务时代,人们在软件的代码层面去解决分布式问题,而在后微服务时代利用Kubernetes可以直接在硬件层面实现对分布式精细管理能力,从而将与业务无关的技术性问题从软件层面剥离,让软件开发可以只专注于业务。
无服务时代:无服务的愿景是让开发者只需要纯粹地关注业务,不需要考虑技术组件,不需要考虑如何部署,不需要考虑算力,也不需要操心运维,这些全部由云计算服务商完成。但目前技术并不是很成熟,对于业务逻辑复杂、依赖服务端状态等问题,还无法胜任。
主要内容
服务架构演进历史:
-
原始分布式时代:
在早起计算机性能严重不足,在为了提升计算机的性能、算力这一背景下,对分布式的一些探索,像是远程的服务在哪里(服务发现),有多少个(负载均衡),网络出现分区、超时或者服务出错了怎么办(熔断、隔离、降级),方法参数与返回结果如何表示(序列化协议),信息如何传输(传输协议),服务权限如何管理(认证、授权),如何保证通信安全(网络安全层),如何调用不同机器的服务返回相同的结果(分布式数据一致性)等等,提出了非常多对未来非常有影响的理论。
-
单体系统时代:
根据维基百科的定义为,"单体意味着自包含。单体应用描述了一种由同一技术平台的不同组件构成的单层软件"。
首先对于小型单体系统的好处很明显,易于开发、测试、部署,且由于系统中各个功能、模块、方法的调用过程都是进程内调用,不会发生进程间通信,所有模块、方法的调用都无需考虑网络分区、对象复制这些麻烦的事和性能损失。因此运行效率很高。
而对于单体系统的不足,必须在软件的性能需求超过了单机、软件的开发人员规模明显超过了"2 Pizza Team"(由亚马逊创始人提出的衡量团队大小的量词,指两个Pizza能喂饱的人数,大概是6~12人)范畴的前提下,才有讨论的价值。
对于大型单体系统,人们会说它是不可拆分的,难以扩展的,因此才不能支撑越来越大的软件规模。这种想法其实是有失偏颇的。从纵向来看,它是遵循分层架构的,收到的外部请求会在各层之间以不同的形式的数据结构进行传递,触及最末端的数据库后按相反的顺序回馈反应;从横向来看,单体架构也支持按照技术、功能、职责等维度,将软件拆分为各种模块,以便重用和管理代码,单体系统并不意味着只能有一个整体的程序封装形式,如果需要,它完全可以由多个 Jar、War或者其他模块格式来构成,在用负载均衡的方式,同时部署若干个相同的单体系统副本,以达到分摊流量压力的效果,这也是非常常见的扩展。它真正的问题其实是一下几点:
1)单体难以阻断错误传播 。在拆分之后,无法做到自治与隔离。任何一部分代码出现缺陷,所造成的影响也是全局性的、难以隔离的,进而影响整个程序,譬如内存泄漏、线程爆炸、阻塞、死循环等。甚至如果出现问题的是某些更高层次的公共资源,譬如端口号或者数据库连接池的泄漏,还将会影响整台机器甚至集群中其它单体副本的正常工作。(上文提到了,用多个War和Jar横向扩展的方式,一个Jar坏了,别的也会坏?这里不是在说这,是在说真正的单体)
2)不便于动态更新程序 。不能隔离(利用OSGi可以,但很复杂),同时也意味着无法做到单独停止、更新、升级某一部分代码。所以可维护性差,并且也不利于灰度发布,A/B测试。
3)面临难以技术异构的困难 。难以隔离也导致了每个模块的代码都通常需要使用一样的程序语言,乃至一样的编程框架去开发(JNI 就可以让Java混用C或C++,但这通常是迫不得已的,并不是优雅的选择)。
以上列举的这些问题都还不是今天以微服务取代单体系统成为潮流趋势的根本原因,作者认为最重要的理由是:这种架构风格潜在的观念是希望系统的每一个部件,每一处代码都尽量可靠,靠不出或少出缺陷来构建可靠系统。然而战术层面再优秀,也很难弥补战略层面的不足,单体靠高质量来保证高可靠性的思路,在小规模软件上还能运作良好,但系统规模越大,交付一个可靠的单体系统就变得越来越具有挑战性。正是随着软件架构演进,构筑可靠系统从"追求尽量不出错",到正视"出错是必然"的观念转变,才是微服务架构得以挑战并逐步开始取代运作了数十年的单体架构的底气所在。
当系统规模小的时候,这是优势,但当系统规模大,或程序需要修改的时候,其部署的成本、技术升级的迁移成本都会变得更为昂贵。为了允许程序出错,为了获得隔离、自治的能力,为了可以技术异构等目标,是继为了性能与算力之后,让程序再次选择分布式的理由。
-
SOA时代:
中文叫面向服务的架构(Service-Oriented Architecture),为了对大型的单体系统进行拆分,让每一个子系统都能独立地部署、运行、更新,在SOA来到之前,开发者们尝试了很多架构,以下是三种有代表性的架构:
1)烟囱式架构,它指的是一种完全不与其他相关信息系统进行互操作或者协调工作的设计模式。用企业和部门来举例子,企业中真的存在完全不发生交互的部门吗?并且这样"独立拆分""老死不相往来"的系统,显然不可能是企业所希望见到的。
2)微内核架构:在烟囱式架构中,没有业务往来关系的系统也可能需要共享人员、组织、权限等一些的公共的主数据,那不妨就将这些主数据,连同其他可能被各子系统使用到的公共服务、数据、资源集中到一块,成为一个被所有业务系统共同依赖的核心,具体的业务系统以插件模块的形式存在,这样可以提供可扩展的、灵活的、天然隔离的功能特性,即微内核架构。
这种模式很适合桌面应用程序,也经常在Web应用程序中使用。不过,微内核架构也有它的局限和使用前提,它假设系统中各个插件模块之间是互不认识,不可预知系统将安装哪些模块,因此这些插件可以访问内核中一些公共的资源,但不会直接交互。可是,无论是企业信息系统还是互联网应用,这一前提假设在许多场景中都并不成立,我们必须找到办法,既能拆分出独立的系统,也能让拆分后的子系统之间顺畅地互相调用通信。
3)事件驱动架构:为了能让子系统互相通信,一种可行的方案是在子系统之间建立一套事件队列管道。来自系统外部的消息将以事件的形式发送至管道中,各个子系统从管道里获取自己感兴趣、需要处理的事件消息。基于此,每一个消息的处理者都是独立的,高度解耦的,但又能与其他处理者通过事件管道进行互动。
在这之后远程服务的调用迎来SOAP协议诞生,基于此,软件也正式进入SOA时代。面对在未来微服务时代的问题,大多数也是在分布式服务刚被提出时就已经可以预见的困难点,SOA都进行了更加系统性、更加具体的探索。
"更具体"体现在尽管SOA有更强的操作性,有清晰软件设计的指导原则,譬如服务的封装性、自治、松耦合、可重用、可组合、无状态等;明确了采用 SOAP 作为远程调用的协议;利用被称为企业服务总线(ESB)的消息管道来实现各个子系统之间的通信交互,等等。在这一整套成体系可以互相精密协作的技术组件支持下,若仅从技术可行性这一个角度来评判的话,SOA可以算是成功地解决了分布式环境下出现的主要技术问题。
"更系统"指的是SOA的宏大理想,它的终极目标是希望总结出一套自上而下的软件研发方法论,希望做到企业只需要跟着SOA的思路,就能够一揽子解决掉软件开发过程中的全部问题,而且SOA不仅关注技术,还关注研发过程中涉及到的需求、管理、流程和组织。
但是过于严格的规范定义带来过度的复杂性。而构建在SOAP基础之上的ESB、BPM等诸多上层建筑,进一步加剧了这种复杂性。开发信息系统毕竟不是作八股文章,过于精密的流程和理论也需要懂得复杂概念的专业人员才能够驾驭。它可以实现多个异构大型系统之间的复杂集成交互,却很难作为一种具有广泛普适性的软件架构风格来推广。最终也没有获得成功。
-
微服务时代:
微服务是一种通过多个小型服务组合来构建单个应用的架构风格,这些服务围绕业务能力而非特定的技术标准来构建。各个服务可以采用不同的编程语言,不同的数据存储技术,运行在不同的进程之中,服务采取轻量级的通信机制和自动化的部署机制实现通信与运维。它有一下几个以下特点:
1)围绕业务能力构建,这点也强调了康威定律的重要性,组织设计系统时会受到组织沟通结构的限制,因此产品必然是其组织沟通结构的缩影 ;2)分散治理,微服务更加强调的是在确实需要技术异构时,应该能有选择"不统一"的权利;3)通过服务来实现独立自治的组件。之所以强调通过"服务"(Service)而不是"类库"(Library)来构建组件,是因为类库在编译期静态链接到程序中,通过本地调用来提供功能,而服务是进程外组件,通过远程调用来提供功能;4)产品化思维,以前在单体架构下,程序的规模决定了无法让全部人员都关注完整的产品,组织中会有开发、运维、支持等细致的分工的成员,各人只关注于自己的一块工作,但在微服务下,要求开发团队中每个人都具有产品化思维,关心整个产品的全部方面是具有可行性的;5)数据去中心化,微服务明确地提倡数据应该按领域分散管理、更新、维护、存储,在单体服务中,一个系统的各个功能模块通常会使用同一个数据库,诚然中心化的存储天生就更容易避免一致性问题,但是,同一个数据实体在不同服务的视角里,它的抽象形态往往也是不同的。譬如,商店里的书本,在销售领域中关注的是价格,在仓储领域中关注的库存数量,在商品展示领域中关注的是书籍的介绍信息,如果作为中心化的存储,所有领域都必须修改和映射到同一个实体之中,这便使得不同的服务很可能会互相产生影响而丧失掉独立性。尽管在分布式中要处理好一致性的问题也相当困难,很多时候都没法使用传统的事务处理来保证,但是两害相权取其轻,有一些必要的代价仍是值得付出的;6)强终端弱管道,弱管道几乎算是直接指名道姓地反对 SOAP 和 ESB 的那一堆复杂的通信机制,这些构筑在通信管道上的功能也许对某个系统中的某一部分服务是有必要的,但对于另外更多的服务则是强加进来的负担。如果服务需要上面的额外通信能力,就应该在服务自己的Endpoint上解决,而不是在通信管道上一揽子处理。微服务提倡类似于经典UNIX过滤器那样简单直接的通信方式,RESTful风格的通信在微服务中会是更加合适的选择;7)容错性设计,要求在微服务的设计中,有自动的机制对其依赖的服务能够进行快速故障检测,在持续出错的时候进行隔离,在服务恢复的时候重新联通;8)演进式设计,容错性设计承认服务会出错,演进式设计则是承认服务会被报废淘汰。一个设计良好的服务,应该是能够报废的,而不是期望得到长存永生。假如系统中出现不可更改、无可替代的服务,这并不能说明这个服务是多么的优秀、多么的重要,反而是一种系统设计上脆弱的表现,微服务所追求的独立、自治,也是反对这种脆弱性的表现;9)基础设施自动化,基础设施自动化,如 CI/CD 的长足发展,显著减少了构建、发布、运维工作的复杂性。由于微服务下运维的对象比起单体架构要有数量级的增长,使用微服务的团队更加依赖于基础设施的自动化,人工是很难支撑成百上千乃至成千上万级别的服务的。
从以上微服务的定义和特征中,应该可以明显地感觉到微服务追求的是更加自由的架构风格,摒弃了几乎所有SOA里可以抛弃的约束和规定。可是,如果没有了统一的规范和约束,以前 SOA 所解决的那些分布式服务的问题,不也就一下子都重新出现了吗?的确如此,这些问题,在微服务中不再会有统一的解决方案,即使只讨论Java范围内会使用到的微服务,光一个服务间远程调用问题,可以列入解决方案的候选清单的就有:RMI(Sun/Oracle)、Thrift(Facebook)、Dubbo(阿里巴巴)、gRPC(Google)等等。
微服务所带来的自由是一把双刃开锋的宝剑,一方面砍掉了SOA定下的复杂技术标准,一个简单服务,并不见得就会同时面临分布式中所有的问题,也就没有必要背上 SOA 那百宝袋般沉重的技术包袱,需要解决什么问题,就引入什么工具。另一方面因为这种自由,软件的架构设计难度大大提升。
-
后微服务时代:
在微服务时代,人们选择在软件的代码层面而不是硬件的基础设施层面去解决这些分布式问题,很大程度上是因为由硬件构成的基础设施,跟不上由软件构成的应用服务的灵活性的无奈之举。软件可以只使用键盘命令就能拆分出不同的服务,只通过拷贝、启动就能够伸缩扩容服务,硬件难道就不可以通过敲键盘就变出相应的应用服务器、负载均衡器、DNS 服务器、网络链路这些设施吗?(也就是让软件仅仅考虑业务就可以了)
解决这个问题的就是容器,然而早期的容器,仅仅可以作为快速启动的服务运行环境,目的是方便程序的分发部署,这个阶段针对单个应用进行封装的容器并未真正参与到分布式问题的解决之中,真正的变革来自于Kubernetes的成功发展,下图是Kubernetes解决分布式问题技术方案与传统微服务方案的对比。尽管因为各自出发点不同,解决问题的方法和效果都有所差异,但这无疑是提供了一条全新的、前途更加广阔的解题思路。
当虚拟化的硬件能够跟上软件的灵活性,那些与业务无关的技术性问题便有可能从软件层面剥离,悄无声息地解决于硬件基础设施之内,让软件得以只专注业务。从软件层面独力应对分布式架构所带来的各种问题,发展到应用代码与基础设施,软、硬一体,合力应对架构问题的时代,现在常被媒体冠以"云原生"这个颇为抽象的名字加以宣传。
但Kubernetes仍然没有能够完美解决全部的分布式问题------"不完美"的意思是,仅从功能上看,单纯的Kubernetes反而不如之前的Spring Cloud方案。这是因为有一些问题处于应用系统与基础设施的边缘,使得完全在基础设施层面中确实很难精细化地处理。举个例子,微服务 A 调用了微服务B的两个服务,称为B1和B2,假设B1表现正常但B2出现了持续的500错,那在达到一定阈值之后就应该对B2进行熔断,以避免产生雪崩效应。如果仅在基础设施层面来处理,这会遇到一个两难问题,切断A到B的网络通路则会影响到B1的正常调用,不切断的话则持续受 B2的错误影响。
以上问题在通过Spring Cloud这类应用代码实现的微服务中并不难处理,既然是使用程序代码来解决问题,只要合乎逻辑,想要实现什么功能,只受限于开发人员的想象力与技术能力,但基础设施是针对整个容器来管理的,粒度相对粗旷,只能到容器层面,对单个远程服务就很难有效管控。
为了解决这一类问题,引入了今天被称为"服务网格"(Service Mesh)的"边车代理模式"(Sidecar Proxy),这个场景里指的具体含义是由系统自动在服务容器(通常是指Kubernetes的Pod)中注入一个通信代理服务器,在应用毫无感知的情况下,悄然接管应用所有对外通信。这个代理除了实现正常的服务间通信外(称为数据平面通信),还接收来自控制器的指令(称为控制平面通信),根据控制平面中的配置,对数据平面通信的内容进行分析处理,以实现熔断、认证、度量、监控、负载均衡等各种附加功能。这样便实现了既不需要在应用层面加入额外的处理代码,也提供了几乎不亚于程序代码的精细管理能力。
很难从概念上判定清楚一个与应用系统运行于同一资源容器之内的代理服务到底应该算软件还是算基础设施,但它对应用是透明的,不需要改动任何软件代码就可以实现服务治理,这便足够了。把"选择什么通信协议"、"怎样调度流量"、"如何认证授权"之类的技术问题隔离于程序代码之外,取代今天Spring Cloud全家桶中大部分组件的功能,微服务只需要考虑业务本身的逻辑,这才是最理想的智能终端解决方案。
-
无服务时代:
无服务现在还没有一个特别权威的"官方"定义,但它的概念并没有前面各种架构那么复杂,本来无服务也是以"简单"为主要卖点的,它只涉及两块内容:后端设施和函数。
后端设施是指数据库、消息队列、日志、存储,等等这一类用于支撑业务逻辑运行,但本身无业务含义的技术组件,这些后端设施都运行在云中,无服务中称其为"后端即服务"。
函数是指业务逻辑代码,这里函数的概念与粒度,很接近于程序编码角度的函数了,其区别是无服务中的函数运行在云端,不必考虑算力问题,不必考虑容量规划,无服务中称其为"函数即服务"。
无服务的愿景是让开发者只需要纯粹地关注业务,不需要考虑技术组件,后端的技术组件是现成的,可以直接取用,没有采购、版权和选型的烦恼;不需要考虑如何部署,部署过程完全是托管到云端的,工作由云端自动完成;不需要考虑算力,有整个数据中心支撑,算力可以认为是无限的;也不需要操心运维,维护系统持续平稳运行是云计算服务商的责任而不再是开发者的责任。
无服务架构的远期前景看起来是很美好的,但做者对无服务中短期内的发展并没有那么乐观。对于那些业务逻辑复杂、依赖服务端状态、响应速度、需要长链接的应用,并不适合。这是因为无服务天生"无限算力"的假设决定了它必须要按使用量计费以控制消耗算力的规模,因而函数不会一直以活动状态常驻服务器,请求到了才会开始运行,这导致了函数不便于去依赖服务端状态,也导致了函数会有冷启动时间,响应的性能不可能太好(目前无服务的冷启动过程大概是在数十到百毫秒级别,对于Java这类启动性能差的应用,甚至能到接近秒的级别)。
关键句
- 架构演变最重要的驱动力,或者说这种"从大到小"的变化趋势的最根本驱动力,始终都是为了方便某个服务能够顺利地"死去"与"重生"。个体服务的生死更迭,是关系到整个体系能否可靠存续的关键因素。
- 架构并不是发明出来的,而是持续演进的结果。
- 正是随着软件架构演进,构建可靠系统的观念从"追求尽量不出错"到正视"出错是必然"的转变,才是微服务架构得以挑战并逐步取代单体架构的底气所在。
- Unix DCE 提出的分布式服务的设计主旨:"让开发人员不必关心服务是远程还是本地,都能够透明地调用服务或者访问资源"。
- 业务与技术完全分离,远程与本地完全透明,也许就是最好的时代了吧。
其它
-
Unix是啥?
简单来讲就是大部分操作系统的鼻祖(比如Windows就不是),以下是知乎的一个回答。
现行的系统Windows、Mac OS、Linux和各种发行版等,除了Windows3.1以后版本的Windows内核是nt内核以外,其他的所有系统及版本,底层都起源于Unix。windows只有1和2,底层和Unix有关,后续版本就完全无关了。其中Mac OS是混合内核的,Unix和卡内基梅隆的一个教授开发的内核的mixing。Linux是完全仿unix的。
提到Unix,就会有很多相关联的词,比如Mac OS、Linux、AIX、HP-UX、Solaris,这些都是类Unix系统。
类Unix系统(英语:Unix-like;经常被称为UNX或 nix)指各种Unix的派生系统,比如FreeBSD、OpenBSD、SUN的Solaris,以及各种与传统Unix类似的系统,例如Minix、Linux、QNX等。它们虽然有的是自由软件,有的是私有软件,但都相当程度地继承了原始UNIX的特性,有许多相似处,并且都在一定程度上遵守POSIX规范。
UNIX的商标权由国际开放标准组织所拥有,只有符合单一UNIX规范的UNIX系统才能使用UNIX这个名称,否则只能称为类UNIX(UNIX-like)。
更简单的图:
-
分层的意义
包括像我们最熟悉的分层架构,MVC架构,模型(Model)-视图(View)-控制器(Controller),这些分层的意义是什么呢?分层设计的本质其实就是将复杂问题简单化,以下是知乎的回答:
高内聚:分层设计可以简化系统设计,让不同层专注做某一模块的事;
低耦合:层与层之间通过接口或API来交互,依赖方不用知道被依赖方的细节;
复用:分层之后可以做到代码或功能的复用;
扩展性:分层架构可以让代码更容易横向扩展
-
JAR、WAR
Jar和War可以看成是文件压缩,这个打包实际上就是把代码和依赖的东西压缩在一起。
Jar包是Java打的包,War包可以理解为JavaWeb打的包。Jar包中只是用Java来写的项目打包来的,里面只有编译后的class和一些部署文件。而War包里面的东西就全了,包括写的代码编译成的class文件,依赖的包,配置文件,所有的网站页面,包括HTML,JSP等等。一个War包可以理解为是一个web项目,里面是项目的所有东西。
-
灰度发布
灰度发布是一种发布方式,它可以让一部分用户继续使用产品特性A,一部分用户开始使用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面。这种方式可以让新功能或更改在不影响整个系统的情况下进行测试和验证。
-
多线程与并发的区别
首先说明,线程是进程的一个执行单元,是进程内调度实体,进程有点像为每个应用程序提供了一个隔离环境。
对于并发和并行,并发是指多个任务在同一时间段内交替执行,并共享计算资源,注重任务的调度和资源管理;并行是指多个任务同时进行,利用多个处理单元或计算资源,并独立进行,注重任务的分割和执行。
多线程的目的一般是为了并行计算,操作系统可以将这些线程分配给多个CPU同时运行。那么单核CPU也就无法实现并行计算。
我突然有个疑问,为什么我们经常听到高并发,而没有高并行呢?
高并发是指系统能够同时处理大量的请求,而高并行是指系统能够同时运行多少个任务。一个请求可能需要多个任务来完成,也可能多个请求共享同一个任务。所以说高并行只是一种实现高并发的手段。
高并发是一个相对于用户或客户端的概念,而高并行是一个相对于服务器或处理器的概念。用户或客户端关心的是系统能否快速响应他们的请求,而不在乎系统内部是如何分配和执行任务的。服务器或处理器关心的是如何利用多核或多机器来提高任务的执行速度。因此,高并发更能反映系统对外的服务质量,而高并行更能反映系统内部的工作方式。