分布式系统
维基百科
维基百科对“分布式系统”的宏观定义是这样的:
分布式系统是一种其组件位于不同的联网计算机上的系统,然后通过互相传递消息来进行通信和协调。为了达到共同的目标,这些组件会相互作用。
我们可以再以大小关系来解释它:把需要进行大量计算的工程数据分割成小块,由多台计算机分别计算,然后将结果统一合并得出数据结论的科学。
这本质上就是“分治”。而“单程序 + 单数据库”组合的系统也包含了至少两个进程,“麻雀虽小五脏俱全”,这也是“分布式系统”。
- 分治
那么分布式系统怎么将任务分发到这些计算机节点呢,很简单的思想,分而治之,即分片(partition)。对于计算,那么就是对计算任务进行切换,每个节点算一些,最终汇总就行了,这就是MapReduce的思想;对于存储,更好理解一下,每个节点存一部分数据就行了。当数据规模变大的时候,Partition是唯一的选择,同时也会带来一些好处:
(1)提升性能和并发,操作被分发到不同的分片,相互独立
(2)提升系统的可用性,即使部分分片不能用,其他分片不会受到影响
理想的情况下,有分片就行了,但事实的情况却不大理想。
原因在于,分布式系统中有大量的节点,且通过网络通信。单个节点的故障(进程crash、断电、磁盘损坏)是个小概率事件,但整个系统的故障率会随节点的增加而指数级增加,网络通信也可能出现断网、高延迟的情况。在这种一定会出现的“异常”情况下,分布式系统还是需要继续稳定的对外提供服务,即需要较强的容错性。最简单的办法,就是冗余或者复制集(Replication),即多个节点负责同一个任务,最为常见的就是分布式存储中,多个节点复杂存储同一份数据,以此增强可用性与可靠性。同时,Replication也会带来性能的提升,比如数据的locality可以减少用户的等待时间。
分布式系统带来的问题
以前看过一句话,没有分布式,你的系统唯一问题就是慢,有了分布式就会有各种问题。
分布式系统需要大量机器协作,面临诸多的挑战:
第一,异构的机器与网络:
分布式系统中的机器,配置不一样,其上运行的服务也可能由不同的语言、架构实现,因此处理能力也不一样;节点间通过网络连接,而不同网络运营商提供的网络的带宽、延时、丢包率又不一样。怎么保证大家齐头并进,共同完成目标,这四个不小的挑战。
第二,普遍的节点故障:
虽然单个节点的故障概率较低,但节点数目达到一定规模,出故障的概率就变高了。分布式系统需要保证故障发生的时候,系统仍然是可用的,这就需要监控节点的状态,在节点故障的情况下将该节点负责的计算、存储任务转移到其他节点
第三,不可靠的网络:
节点间通过网络通信,而网络是不可靠的。可能的网络问题包括:网络分割、延时、丢包、乱序。
相比单机过程调用,网络通信最让人头疼的是超时:节点A向节点B发出请求,在约定的时间内没有收到节点B的响应,那么B是否处理了请求,这个是不确定的,这个不确定会带来诸多问题,最简单的,是否要重试请求,节点B会不会多次处理同一个请求。
分布式系统的常见想法
“分布式系统”等于 SOA、ESB、微服务这些东西吗?
如果你一下子想到的是 XX 中心、XX 服务,意味着你把服务化的模式(SOA、ESB、微服务)和分布式系统错误地划上了等号。
那么,什么是“服务化”呢?服务化就像企业当中将相同岗位的人员划分到同一个部门管理,以此来收敛特定的工作入口,再进行二次分配,以提高人员利用率和劳动成果的复用度。服务化的本质是“分治”,而“分治”的前提是先要拆,然后才谈得上如何治。这时,高内聚、低耦合的思想在拆分过程中起到了一个非常重要的作用,因为这可以尽可能地降低拆分后不同组件间进行协作的复杂度。所以重要的是“怎么拆“,还有如何循序渐进地拆,而这个过程中你究竟是采用了何种服务化模式(比如 SOA、ESB、微服务等)并不是关键。
为什么说“怎么拆”最重要呢?我来举个例子,企业的组织架构包括三种模型:职能型、项目型、矩阵型。你可以把这里的企业理解为一个“分布式系统”,把后面的 3 种模型理解为这个分布式系统的 3 种形态。作为这个“系统”的所有人,你需要考虑如何拆分它,才能使得各功能组件相互之间可以更好地协作。
可以看到服务化体现了“分治”的效果,这也是分布式系统的核心思想,因此从“分治”这个本质上来看,服务化的确是分布式系统,但分布式系统不仅仅停留在那些服务化的模式上。
我相信,你在工作中参与开发的任何软件系统,到处都存在着需要拆分的地方,除非它的功能极简到只需要计算一个 1+1。比如,当我们在电商平台点击“提交订单”的时候,会涉及生成订单、扣除积分、扣除库存等等动作。电商系统初期所有的功能可能都在一个系统里面,那么这些操作可以写在一个方法体里吗?我想只要代码能够成功运行,大部分人是不会管你怎么写的。但是如果这时需要增加一个红包功能呢?相信你或多或少遇到过在几百上千行代码中去增改功能的事情,其中的痛苦应该深有体会。
要解决这个问题就是要做拆分,通过梳理、归类,将不同的紧密相关的部分收敛到一个独立的逻辑体中,这个逻辑体可以是函数、类以及命名空间,等等。所以,从这个角度来说“分治”的问题其实早就存在我们的工作中,就看我们是否有去关注它了。因此,这并不只是我们在进行服务化时才需要考虑的问题。
那么如何才能做好这个事情,更好的拆分能力正是我们需要掌握的。如果只是因为看到其他人这么拆,我也这么拆,根据“二八原则”,或许“依样画葫芦”可以达到 80% 的契合度,但是往往那剩下的 20% 会是耗费我们 80% 精力的“大麻烦”。要知道,只有掌握了核心主旨,才能更快地找到最理想的高内聚、低耦合方案。
分布式系统”是各种中间件吗?
又或许,听到分布式系统,你想到了某某 MQ 框架、某某 RPC 框架、某某 DAL 框架,把运用中间件和分布式系统错误地划上了等号。
这里需要搞清楚的是,中间件起到的是标准化的作用。
中间件只是承载这些标准化想法的介质、工具,可以起到引导和约束的效果,以此起到大大降低系统复杂度和协作成本的作用。
我们来分别看一下:
MQ 框架标准化了不同应用程序间非实时异步通信的方式。
RPC 框架标准化了不同应用程序间实时通讯的方式。
DAL(Data Access Layer,数据访问层)框架标准化了应用程序和数据库之间通讯的方式。
所以,虽然分布式系统中会运用中间件,但分布式系统却不仅仅停留在用了什么中间件上。你需要清楚每一类中间件背后是对什么进行了标准化,它的目的是什么,带来了哪些副作用,等等。只有如此,你才能真正识别不同技术框架之间的区别,找到真正适合当前系统的技术框架。
那么标准是拍脑袋决定的吗?肯定不是,正如前面所说每一次标准化都是有目的的,需要产生价值。比如,大部分中间件都具备这样一个价值:
为了在软件系统的迭代过程中,避免将精力过多地花费在某个子功能下众多差异不大的选项中。
在现实中,这点更多时候出现在技术层面的中间件里,比如,数据库访问框架的作用是为了标准化操作不同数据库的差异,使得上层应用程序不用纠结于该怎么与 mysql 交互或者该怎么与 SQL SERVER 交互。
因为与业务相比,技术层面“稳定”多了,所以做标准化更有价值,更能获得长期收益。但“稳定”是相对的,哪怕单纯在业务层面也存在相对稳定的部分。
海市蜃楼般的“分布式系统”
我相信,自从进入移动时代以来,各种高大上的系统架构图越来越频繁地出现,你的眼前充斥着各种主流、非主流的眼花缭乱的技术框架。
你不由得肃然起敬一番,心中呐喊着:“对,这就是我想去的地方,我想参与甚至实现一个这样牛逼的分布式系统,再也不想每天只是增删改查了。”
得不到的事物总是美好的,但往往我们也会过度地高估它的美好。与此类似,高大上的架构图背后呈现的系统的确也是一个成熟分布式系统的样貌,但我们要清楚一点:罗马不是一日建成的。
而且,“分布式”这个词只是意味着形态上是散列状的,而“一分为二”和“一分为 N”本质上并没有区别。
所以,很多小项目或者大型项目的初期所搭配的基础套餐“单程序 + 单数据库”,同样可以理解为分布式系统,其中遇到的问题很多同样也存在于成熟的分布式系统中。
想象一下,下面的场景是否在“单程序 + 单数据库”项目中出现过?
log 记录执行成功,但是数据库的数据没发生变化;
进程内的缓存数据更新了,但是数据库更新失败了。
这里我们停顿 30 秒,思考一下为什么会出现这些问题?
- 软件是什么?
这里需要我们先思考一下“软件”是什么。 软件的本质是一套代码,而代码只是一段文字,除了提供文字所表述的信息之外,本身无法“动”起来。但是,想让它“动”起来,使其能够完成一件我们指定的事情,前提是需要一个宿主来给予它生命。这个宿主就是计算机,它可以让代码变成一连串可执行的“动作”,然后通过数据这个“燃料”的触发,“动”起来。这个持续的活动过程,又被描述为一个运行中的“进程”。
那么除了我们开发的系统是软件,数据库也是软件,前者负责运算,后者负责存储运算后的结果(也可称为“状态”),分工协作。
所以,“单程序 + 单数据库”为什么也是分布式系统这个问题就很明白了。因为我们所编写的程序运行时所在的进程,和程序中使用到的数据库所在的进程,并不是同一个。也因此导致了,让这两个进程(系统)完成各自的部分,而后最终完成一件完整的事,变得不再像由单个个体独自完成这件事那么简单。这就如“两人三足”游戏一样,如何尽可能地让外部看起来像是一个整体、自然地前进。
所以,我们可以这么理解,涉及多个进程协作才能提供一个完整功能的系统就是“分布式系统”。
那么再回到上面举例的两个场景,我们在思考“单程序 + 单数据库”项目中遇到的这些问题背后的原因和解决它的过程时,与我们在一个成熟的分布式系统中的遭遇是一样的,例如数据一致性。当然,这只是分布式系统核心概念的冰山一角。
分布式系统特性与衡量标准
透明性:使用分布式系统的用户并不关心系统是怎么实现的,也不关心读到的数据来自哪个节点,对用户而言。
分布式系统的最高境界是用户根本感知不到这是一个分布式系统,在《Distributed Systems Principles and Paradigms》一书中, 作者是这么说的:
A distributed system is a collection of independent computers that appears to its users as a single coherent system.
可扩展性:分布式系统的根本目标就是为了处理单个计算机无法处理的任务,当任务增加的时候,分布式系统的处理能力需要随之增加。简单来说,要比较方便的通过增加机器来应对数据量的增长,同时,当任务规模缩减的时候,可以撤掉一些多余的机器,达到动态伸缩的效果
可用性与可靠性:一般来说,分布式系统是需要长时间甚至7*24小时提供服务的。可用性是指系统在各种情况对外提供服务的能力,简单来说,可以通过不可用时间与正常服务时间的必知来衡量;而可靠性而是指计算结果正确、存储的数据不丢失。
高性能:不管是单机还是分布式系统,大家都非常关注性能。不同的系统对性能的衡量指标是不同的,最常见的:高并发,单位时间内处理的任务越多越好;低延迟:每个任务的平均时间越少越好。这个其实跟操作系统CPU的调度策略很像
一致性:分布式系统为了提高可用性可靠性,一般会引入冗余(复制集)。那么如何保证这些节点上的状态一致,这就是分布式系统不得不面对的一致性问题。一致性有很多等级,一致性越强,对用户越友好,但会制约系统的可用性;一致性等级越低,用户就需要兼容数据不一致的情况,但系统的可用性、并发性很高很多。
概念与实现
那么对于上面的各种技术与理论,业界有哪些实现呢,下面进行简单罗列。
当然,下面的这些实现,小部分我用过,知其所以然;大部分听说过,知其然;还有一部分之前闻所未闻,分类也不一定正确,只是从其他文章抄过来的。罗列在这里,以便日后或深或浅的学习。
负载均衡: Nginx:高性能、高并发的web服务器;功能包括负载均衡、反向代理、静态内容缓存、访问控制;工作在应用层
LVS: Linux virtual server,基于集群技术和Linux操作系统实现一个高性能、高可用的服务器;工作在网络层
webserver: Java:Tomcat,Apache,Jboss
Python:gunicorn、uwsgi、twisted、webpy、tornado
service: SOA、微服务、spring boot,django
容器: docker,kubernetes
cache: memcache、redis等
协调中心: zookeeper、etcd等
zookeeper使用了Paxos协议Paxos是强一致性,高可用的去中心化分布式。zookeeper的使用场景非常广泛,之后细讲。
rpc框架: grpc、dubbo、brpc
dubbo是阿里开源的Java语言开发的高性能RPC框架,在阿里系的诸多架构中,都使用了dubbo + spring boot
消息队列: kafka、rabbitMQ、rocketMQ、QSP
消息队列的应用场景:异步处理、应用解耦、流量削锋和消息通讯
实时数据平台: storm、akka
离线数据平台: hadoop、spark
PS: apark、akka、kafka都是scala语言写的,看到这个语言还是很牛逼的
dbproxy: cobar也是阿里开源的,在阿里系中使用也非常广泛,是关系型数据库的sharding + replica 代理
db: mysql、oracle、MongoDB、HBase
搜索: elasticsearch、solr
日志: rsyslog、elk、flume
参考资料
https://mp.weixin.qq.com/s/eCpJkydT87U7UwvzvOdcGA