演进中的架构
这是目前最震撼我的一篇总结。过于高屋建瓴,我就不指指点点了:D
可靠的系统
什么是可靠的系统?系统可靠不在于每一部分都可靠,而在于不可靠的地方在出错崩溃之后能自动被取代,以继续维持系统的整体稳定。这样即使用不可靠的部件,依旧能构造出可靠的系统。
墨菲定律(Murphy’s Law):如果事情可能出错就总会出错(Anything that can go wrong will go wrong)。
所以该来的一定会来,跑不掉的,与其祈求不出错,不如做好重生措施。
所以哪怕是由极不靠谱的人员开发的极不靠谱的程序,哪怕存在严重的内存泄漏,哪怕服务三分钟就崩溃,只要能有多个实例并在失败后不断重建,从外部看来依然(有可能)是稳定健壮的。
架构的发展
架构不是被发明出来的,而是持续演进的结果。因为当前架构无法满足当前需求,所以才发生改变,而不是被凭空创造出来的。
原始分布式
虽然最简单的服务是单体,但一开始(1970+、1980+)人们其实是在尝试“使用多个独立的分布式系统共同构建一个更大型的系统”。虽然听起来匪夷所思,但这是当时的硬件条件决定的。当时的微型计算机(Intel 8086 processor)有限的算力让人不得不提出这样的构想:构建符合UNIX设计哲学的、如同本地调用一般简单透明的分布式系统。
然而该构想终究要失败,因为分布式系统天然存在诸多难题,远比单体系统复杂,所以在当时的条件下几乎是不可能完成的:
- 服务发现:远程服务在哪里
- 负载均衡:有多少个实例
- 熔断、隔离、降级:网络出现分区、超时,服务出错,怎么办
- 序列化:方法的参数与返回值
- 传输协议:信息如何传输
- 认证授权:如何认证,如何鉴权
- 网络安全层:如何安全通信。当时还没有SSL/TLS
- 数据一致性:调用不同的机器如何返回一致的结果
- 等等
而这些问题在当时几乎都没有解决方案。经过这么多年的发展和研究,现在才有了种种解决方案,或者CAP这样的理论支撑。这就叫历史局限性,把现在的任何人放到当时的时代,都不可能复现现在的分布式架构。不经过几十年的探索,历史不进步,个人是无能为力的。
将一个系统拆分到不同的机器中运行,这样做带来的服务发现、跟踪、通信、容错、隔离、配置、传输、数据一致性和编码复杂度等方面的问题,所付出的代价远远超过了分布式所取得的收益。
看了这段历史,相信没有谁再敢不考虑实际情况上来就说分布式架构要比单体好。
Kyle Brown:某个功能能够进行分布式,并不意味着它就应该进行分布式,强行追求透明的分布式操作,只会自寻苦果(Just because something can be distributed doesn’t mean it should be distributed. Trying to make a distributed call act like a local call always ends in tears)。
单体
好在80年代是摩尔定律发挥作用的黄金年代,硬件性能飞速发展,用单体应用支撑起稳定的服务成为了可能。
单体是源于本质的优秀
单体系统比分布式简单很多很多,但简单并不代表落后。单体系统是一个人写程序时无需刻意学习就自然写出来的一种“架构”,是源自本能的优秀设计。
单体是如此自然而然,以至于直到微服务流行开来之后,单体作为对照才被认为是一种“架构”。
单体系统的方法都是进程内调用,不会发生进程间通信(IPC,Inter-Process Communication),不仅避免了上述分布式系统的复杂问题,而且运行起来效率是最高的。
大型单体
虽然进程内调用简单高效,但是不自治、不隔离。如果一部分代码出现缺陷,过度消耗进程内的资源,会导致其他代码一块儿凉凉。而如果能自治,就只影响自己,影响范围可控。
单体的缺点,必须放在“大型”单体应用这个前提下才成立。当单体系统复杂起来,可维护性就变差了,因为不可能停掉半个进程做更新。为此出现了OSGi、JVMTI等复杂的HotSwap方案,但这就像是给奔跑中的汽车更换轮胎,用起来复杂而别扭。
太多这样的例子了,比如upnp使用http over udp,虽然放弃了tcp的可靠传输,但是在家庭局域网的场景下,几乎没什么影响,还能借助udp的multicast让设备即插即用,不需要任何配置。但是放在互联网里它就不合适了,http传输就得用http over tcp。
单体系统的不自治,使得它很难符合上述“可靠的系统”。规模越大,让单体系统不出错的概率越小。而由墨菲定律可知,哪怕有一丁点儿出错的可能,出错最终也是必然的,更何况出错的概率本身就越来越大。为了允许程序出错,为了获得隔离、自治的能力,为了可以技术异构等目标,是继为了性能与算力之后,让程序再次选择分布式的理由。
微服务
尽管远程服务有高昂的调用成本,但这是为组件带来自治与隔离能力的必要代价。
所谓系统检修,不过是一次在线服务更新而已。
后微服务(云原生)
其实微服务中碰到的上述分布式问题,未必一定要由软件解决,硬件也是可以的,比如:
- 伸缩扩容:通过在新的服务器部署新的服务实例来解决;
- 服务发现:通过部署DNS服务器来解决;
- 安全传输:通过TLS和CA证书来解决;
人们之所以选择在软件层面去解决分不是问题,是因为硬件设施没有软件灵活:软件只需要敲敲键盘就可以copy并启动新的实例以实现伸缩扩容,硬件没法通过敲敲键盘变出新的服务器。
所以现在的微服务技术分成了两条路:
- 虚拟化技术+容器化技术:比如Kubernetes,虚拟化基础设施(硬件),想要在基础设施层面解决微服务中的问题,让应用层面不再考虑这些问题;
- 传统软件层面的解决方案:比如spring cloud,从应用层面解决微服务的问题;
比如:
- 硬件层面解决服务发现:用的是DNS服务器;
- 虚拟化硬件层面解决服务发现:KubeDNS/CoreDNS;
- 应用层面解决服务发现:spring cloud Eureka;
应用层面的解决方案使得应用不得不直面分布式的问题(只不过被spring cloud封装了),本质上这是对应用开发的一种“侵入”(只不过通过spring cloud的工作,侵入性变小了)。虚拟化的基础设施从虚拟化单个容器到虚拟化多个容器构成的集群、通信网络、存储设施,假设虚拟化的硬件能够跟上软件的灵活性,那么和业务无关的分布式问题就可以在虚拟化硬件层面解决,软件可以专注业务。
服务网格(Service Mesh)
就目前来看,kubernetes并不如spring cloud完美,因为有些问题很难在基础设施层做精细化处理。
比如某个微服务一直返回50x,实际上是内部出了问题,应该熔断,软件层面很好判断。但虚拟化的基础设施是针对整个容器来管理的,粒度过粗,只能到容器层面,很难深入到response的返回值。
为了解决这一问题,虚拟化硬件引入了服务网格service mesh,也叫边车代理模式Sidecar Proxy:由系统在服务容器(kubernetes的pod)里自动注入一个通信代理服务器(像不像边三轮的挎斗),以类似中间人攻击的方式劫持服务容器的流量(现在一个pod里有两个container)。由于sidecar是软件,可以提供精细的流量处理、接收来自控制器的指令,实现熔断等操作。
没错,正是曼岛TT的sidecar,边三轮!
从长远来看,kubernetes可能如现在的linux一样,成为服务端的标准运行环境,从而淘汰spring cloud等对业务产生侵入的纯软件层面的解决方案。
sidecar
kubernetes的一个服务有多个pod,每个pod一般只有一个container,但是也可以有多个container。比如带sidecar的pod就是拥有两个container的pod。
添加sidecar本质上就是在服务所在的pod里再起一个容器。比如,如果想收集服务的日志(假设使用filebeat),一般的做法就是:
- 在服务所在的pod里再创建一个filebeat容器;
- 并让服务容器和filebeat容器挂载同一个volume,这样filebeat就能读到服务容器的日志。之后filebeat按照配置把关心的某些日志发往logstash或kafka等组件;
如果不需要保留日志,这个volume可以使用ephemeral volume,既做到数据共享,又能够不持久化存储:Ephemeral Volume通常用于存储短暂的数据,例如日志文件、临时文件等。另外,Ephemeral Volume还可以用于在容器之间共享数据。例如,多个容器可以共享一个Ephemeral Volume,以便在它们之间传递数据或共享状态。
在这里附上在rancher上使用filebeat的流程,一般的做法是:
- 在rancher上使用(kubernetes的)configmap创建配置文件:其实就是创建一对key value,key为
filebeat.yml
,value为文件内容; - 把configmap作为一个volume,挂载在filebeat的容器上;
- 挂载点要看filebeat容器读取配置的位置。如果从
/filebeat/filebeat.yml
读取配置文件,那么configmap的挂载点就是/filebeat
。configmap里也可能有多个文件(多对key value,每一对是一个文件及其对应的内容),如果指向把configmap里的filebeat.yml
挂载上去,可以在挂载时设置sub path in volume设置为filebeat.yml
,挂载后的/filebeat
目录下只会有filebeat.yml
一个文件;
如果只想将其中的一个子目录挂载到容器中,可以使用
subPath
参数来指定子目录的路径,从而实现只挂载volume的某个文件或文件夹到容器。
无服务(器)serverless/云计算
一开始搞分布式是因为单台机器性能不足,后来搞分布式主要是为了容错能力,当然也能获得更好的性能。serverless则从另一个角度出发:如果你想要几乎无尽的性能,同时又不想采用复杂的分布式方案,那么花钱就行。比如亚马逊的lambda serverless计算平台、阿里云腾讯云的serverless。
serviceless才是无服务,serverless是无服务器。翻译的有问题。
serverless以简单为卖点,涉及两块内容:
- backend:指后端的设施如db、mq、log等都在云里,BaaS(backend as a service,后端即服务),不用操心自己部署;
- function:指业务逻辑代码。FaaS(function as a service,函数即服务)。开发者只需要关注纯业务就行了;
怪不得亚马逊取名lambda。function、lambda。
serverless走的是一条非分布式路线,只不过它是云端的单体。
不过serverless更大的意义在于云,和单体或分布式没有绝对的绑定关系。那么在未来分布式+云(用云函数实现微服务架构)也是很有可能的。
总结
架构不是被发明出来的,而是持续演进的结果。虽然很难想象以后架构会发展成什么样,但我们只需要选择当下合适的架构解决自己的问题就行了。
Alan Turing:尽管目光所及之处,只是不远的前方,即使如此,依然可以看到那里有许多值得去完成的工作在等待我们(We can only see a short distance ahead, but we can see plenty there that needs to be done)。