分布式系统经典基础理论
站在全局角度看,分布式系统的本质是什么?简单划归两点:“分治”和“冗余”。在分布式系统设计中,我们需要思考下面的问题和理论:
第一,分治和冗余使得分布式系统具备了核心价值,那么它的价值是什么?
第二,分布式系统设计的两大思路是什么?什么是CAP定理?什么是BASE理论?
分布式系统的目标是提升系统的整体性能和吞吐量另外还要尽量保证分布式系统的容错性,例如增加10台服务器才达到单体系统运行效果2倍左右的性能,那么这个分布式系统就根本没有存在的意义。
即使采用了分布式系统,也要尽力运用并发编程、高性能网络框架等等手段提升分布式节点上的程序性能。
1.分布式系统的价值
谈到分布式系统的价值,可能就得从1953年说起了。在这一年,埃布·格罗希(Herb Grosch)提出了一个他观察得出的规律——Grosch 定律。维基百科中是这样描述的:
计算机性能随着成本的平方而增加。如果计算机 A 的成本是计算机 B 的两倍,那么计算机 A 的速度应该是计算机 B 的四倍。
这一论断与当时的大型机技术非常吻合,因而使得许多机构都尽其所能购买最大的单个大型机。其实,这也非常符合惯性思维,简单粗暴。
然而,1965 年高登·摩尔(Gordon Moore)提出了摩尔定律。经过几年的发展,人们发现摩尔定律的预测是符合现实的。这就意味着,集中式系统的运算能力每隔一段时间才能提升一倍。
那么,到底要隔多久呢?这个“时间”有很多版本,比如广为流传的 18 个月版本,以及 Gordon Moore 本人坚持的 2 年版本。这里我们不用太过纠结于实际情况到底是哪个“时间”版本,因为这其中隐含的意思更重要,即:
如果你的系统需承载的计算量的增长速度大于摩尔定律的预测,那么在未来的某一个时间点,集中式系统将无法承载你所需的计算量。
而这只是一个内在的因素,真正推动分布式系统发展的催化剂是“经济”因素。
人们发现,用廉价机器的集合组成的分布式系统,除了可以获得超过CPU发展速度的性能外,花费更低,具有更好的性价比,并且还可以根据需要增加或者减少所需机器的数量。
所以,我们得到一个新结论:
无论是要以低价格获得普通的性能,还是要以较高的价格获得极高的性能,分布式系统都能够满足。并且受规模效应的影响,系统越大,性价比带来的收益越高。
之后,进入到互联网快速发展的时期,我们看到了分布式系统相比集中式系统的另一个更明显的优势:更高的可用性。例如,有10个能承载10000流量的相同的节点,如果其中的2个故障,只要实际流量不超过8000,系统依然能够正常运转。
而这一切的价值,都是建立在分布式系统的“分治”和“冗余”之上的。
1.1.分治
分治,字面意思是“分而治之”,和我们的大脑在解决问题时的思考方式是一样的。我们可以将整个过程分为3步:分解、治理、归并。而分治思想的表现形式多样,分层、分块都是它的体现。
这么做的好处是:问题越小越容易被解决,并且,只要解决了所有子问题,父问题就都可以被解决了。但是,这么做的时候,需要满足一个最重要的条件:
不同分支上的子问题,不能互相依赖,需要各自独立。
因为一旦包含了依赖关系,子问题和父问题之间就失去了可以被“归并”的意义。在软件开发领域,我们把这个概念称为“耦合度”和“内聚度”,这两个度量概念非常重要。
耦合度,指的是软件模块之间相互依赖的程度。比如,每次调用方法A之后都需要同步调用方法B,那么此时方法A和B间的耦合度是高的。
内聚度,指的是模块内的元素具有共同点的相似程度。比如,一个类中的多个方法有很多的共同之处,都是做支付相关的处理,那么这个类的内聚度是高的。
内聚度通常与耦合度形成对比。低耦合通常与高内聚相关,反之亦然。
所以,当打算进行分治的时候,耦合度和内聚度就是需要考虑的重点。
下面通过一个例子,体会一下耦合度和内聚度的含义。
假设一个电商平台,为了应对更大的访问量,需要拆分一个同时包含商品、促销的系统。如果垂直拆分,是这样:
而如果水平拆分,则是这样的:
假如面对的场景仅仅是具体的商品详情展示页面,很显然,用水平拆分的效果会更好。因为传统的商品展示必然会同时展示促销,所以,如果用水平拆分,一次请求即可以获取所有数据,内聚度非常高,并且此时模块间完全没有耦合。而如果是垂直拆分的话,就需要同时请求2个节点的数据并进行组合,因此耦合度更高、内聚度更差。
但是,这样的假设在真实的电商场景中是不存在的。从全局来看,订单、购物车、商品列表等许多场景也需要促销信息。并且这个时候我们发现引入了一些新的主体,诸如订单、购物车、商品分类等等。这个时候,水平拆分带来的好处越来越小,因为这样只解决了多个耦合中的一个,低耦合丧失了。并且随着商品和促销与外界的关联越来越多,必然有些场景仅仅涉及到商品和促销的其中一个,但是处理的时候,我们还需要避免受到另一个的影响。如此,高内聚也丧失了。
这个时候,反而通过垂直拆分可以获得更优的耦合度和内聚度,如下图:
这个时候,最高的耦合关系从原先的6降到了4,并且商品和促销各自的处理互相不受影响。
所以,会发现随着业务的变化,耦合度与内聚度也会发生变化。因此,及时地进行梳理和调整,可以避免系统的复杂度快速增长,才能最大程度的发挥“分治”带来的好处。
综上,分治可以简化解题的难度,通过高内聚、低耦合的协作关系达到更好“性能与经济比”,来承载更大的流量。而“冗余”则带来了系统可以7*24小时不间断运作的希望。
1.2.冗余
这里的冗余并不等同于代码的冗余、无意义的重复劳动,而是我们有意义去做、人为增加的重复部分。其目的是容许在一定范围内出现故障,而系统不受影响,如下图:
此时,我们可以将冗余的节点部署在一个独立的环境中。这个独立的环境,可能是处于同一个局域网内的不同主机,也可以是不同的局域网,还可能是在不同的机房。很显然,它们能够应对的故障范围是逐步递增的。
但是,像这种单纯地为了备用而做的冗余,最大的弊端是,如果没有出现故障,那么冗余的这部分资源就白白浪费了,不能发挥任何作用。所以,我们才提出诸如双主多活、读写分离之类的概念,以提高资源利用率。
当然,除了软件层面,硬件层面的冗余也是同样的道理。比如,磁盘阵列可以容忍几块之内磁盘损坏,而不会影响整体。
不过也很显然,当故障影响范围大于你冗余的容量时,系统依然会挂。所以,既然你无法预知故障的发生情况,那么做冗余的时候需要平衡的另一端就是成本。相比更多的冗余,追求更好的性价比更合理一些。
在我们生活中的冗余也到处存在的。比如,大部分的飞机和直升机的发动机都是偶数的,汽车中的电子控制系统的冗余机制等。就好比替身与真身的关系,冗余的就是替身。它可以和真身同时活动,也可以代替真身活动。
分治和冗余讲究的都是分散化,最终形成一个完整的系统还需要将它们“链接”起来。天下没有免费的午餐,获得分布式系统价值的同时,这个“再连接”的过程就是我们相比集中式系统要做的额外工作。
1.3.再连接
如何将拆分后的各个节点再次连接起来,从模式上来说,主要是去中心化与中心化之分。
去中心化:消除了中心节点故障带来的全盘出错的风险,却带来了更高的节点间协作成本。
中心化:通过中心节点的集中式管理大大降低了协作成本,但是一但中心节点故障则全盘出错。
另外,从技术角度来说,如何选择通信协议和序列化机制,也是非常重要的。
虽然,很多通讯协议和序列化机制完全可以承担任何场景的连接责任,但是不同的协议和序列化机制再适合的场景才能发挥它最大的优势。比如,需要更高性能的场景运用TCP协议优于HTTP协议;需要更高吞吐量的场景运用UDP协议优于TCP协议,等等。
1.4.总结
不管系统的规模发展到多大,合理地拆分,加上合适的连接方式,那么至少会是一个运转顺畅、协作舒服的系统,至少能够正常发挥分布式系统应有的价值。
至今,发现分布式系统还可以发挥更多的作用。
比如,只要基于一个统一的上层通信协议,其下层的不同节点可以运用不同的技术栈来发挥不同技术各自的优势,比如用Go来应对高并发场景,用Python来做数据分析等。
再比如,提交交付的速度,如下图:
通过分配不同的团队、人员同时进行多个模块的开发,虽然总的耗时增加了,但是整体的交付速度加快了。
事物最本质的东西是恒定的、不变的,可以指引我们的工作方向。分布式系统的本质也是这样,例如,这样的“分治”方案耦合度和内聚度是否最优,这样做“冗余”带来的收益是否成本能够接受。只要持续带着这些思考,我们就像拿着一杆秤,基于它,我们就可以去衡量各种变量影响,然后作权衡。比如成本、时间、人员、性能、易维护等等。也可以基于它去判断什么样的框架、组件、协议更合适当前的环境。
需要不断的权衡,也意味着分布式系统的设计工作不是一步到位,而是循序渐进的。因为过分为未知的未来做更多的考量,最终可能都会打水漂。所以,建议以多考虑1~2步为宜。假如以你所在的团队中对重大技术升级的频率来作为参考的话,可以提供2个升级周期的设计,花一个升级周期的时间先实现第一阶段,下个阶段可以选择直接实现剩下部分,也可继续进行2个升级周期设计,开启一个循环,持续迭代,并且不断修正方向以更贴近现实的发展,就如下图这样。
2.分布式系统设计的两大思路
分布式系统设计的两大思路分别是:中心化和去中心化。
之前在分布式系统的价值中也提到过中心化和去中心化,分别说明了它们的优点和缺陷。
2.1.中心化设计
•两个角色
中心化的设计思想很简单,分布式集群中的节点机器按照角色分工,大体上分为两种角色:master和node。
•角色职责
master通常负责分发任务并监督node,发现哪个node空闲,就想方设法地给空闲的node安排任务,确保没有一个node能够偷懒,如果master发现某个node出现故障,则是不会考虑恢复对应故障的node,而是考虑降级或者剔除,然后把故障node的任务分派给其他正常运行的node。其中微服务架构k8s就恰好采用了这一设计思路。
•中心化设计的问题
第一,中心化的设计存在的最大问题是master的存活问题,如果master出现故障,则群龙无首,整个集群就崩溃了。但我们难以同时安排两个master以避免单点问题。
第二,中心化的设计还存在另外一个潜在的问题,既master的性能问题:可以领导10个人高效工作并不意味着可以领导100个人高效工作,所以如果系统设计和实现得不好,问题就会卡在master身上。
•master存活问题的解决办法
大多数中心化系统都采用了主备两个master的设计方案,可以是热备或者冷备,也可以是自动切换或者手动切换,而且越来越多的新系统都开始具备自动选举切换master的能力,以提升系统的可用性。
2.2.去中心化设计
•众生地位平等
在去中心化的设计里,通常没有master和node这两种角色的区分,大家的角色都是一样的,地位是平等的,全球互联网就是一个典型的去中心化的分布式系统,联网的任意节点设备宕机,都只会影响很小范围的功能。
•”去中心化“不是不要中心,而是由节点来自由选择中心
集群的成员会自发的举行“会议”选举新的领导主持工作。最典型的案例就是Zookeeper及Go语言实现的Etcd。
•去中心化设计的问题
去中心化设计里最难解决的一个问题就是“脑裂”问题,这种情况的发生概率很低,但影响很大。“脑裂”是指一个集群由于网络故障,被分为至少2个彼此无法通信的单独集群,此时如果两个集群都各自工作,则可能会产生严重的数据冲突和错误。一般的设计思路是,当集群判断发生了网络故障时,规模较小的集群就下线或者拒绝服务。
2.3.分布式和集群的区别
•分布式:一个业务拆分成多个子业务,部署在不同的服务器上•集群:同一个业务,部署在多个服务器上。比如,Redis集群和Solr集群都是属于将Redis提供的缓存服务以及Solr服务器提供的搜索服务部署在多个服务器上,提高系统性能、并发量解决海量存储问题。
3.CAP定理
在计算机科学中, CAP定理(CAP theorem), 又被称作 布鲁尔定理(Brewer's theorem), 它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
•一致性(Consistency) (所有节点在同一时间具有相同的数据)•可用性(Availability) (保证每个请求不管成功或者失败都有响应)•分区容错性(Partition tolerance) (系统中任意信息的丢失或失败不会影响系统的继续运作)
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
•CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。•CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。•AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
4.BASE理论
BASE:Basically Available, Soft-state, Eventually Consistent。由 Eric Brewer 定义。
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。
BASE是NoSQL数据库通常对可用性及一致性的弱要求原则:
•Basically Availble --基本可用•Soft-state --软状态/柔性事务。"Soft state" 可以理解为"无连接"的, 而 "Hard state" 是"面向连接"的•Eventual Consistency -- 最终一致性, 也是 ACID 的最终目的。
BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
4.1.BASE理论的核心思想
即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使得系统达到最终一致性。也就是牺牲数据的一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需要保持系统整体“主要可用”。
针对数据库领域,BASE思想的主要实现是对业务数据进行拆分,让不同的数据分布在不同的机器上,以提升系统的可用性,当前主要有以下两种做法:
•按功能划分数据库•分片,如开源的Mycat、Amoeba等
由于拆分后会涉及分布式事务问题,所以需要思考的是如何用最终一致性的思路来实现高性能的分布式事务。
4.2.Basically Availble 基本可用
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。相比于正常系统而言允许出现以下损失:
•响应时间上的损失
正常情况下,搜索引擎0.5s即返回给用户结果,而基本可用的搜索引擎可以在2s左右返回结果。
•系统功能上的损失
正常情况下,电子商务网站上进行购物的时候,消费者能够顺畅的完成每一笔订单。但在大促期间,为了保护电子商务系统的稳定性,部分消费者可能会被引导到一个降级页面。
4.3.Soft-state 软状态/柔性事务
相对于原子性ACID而言,要求多个节点的数据副本都是一致的,这是一种“硬状态”。
软状态是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步过程存在延时。
4.4.Eventual Consistency 最终一致性
最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
亚马逊首席技术官Werner Vogels在于2008年发表的一篇文章中对最终一致性进行了非常详细的介绍。他认为最终一致性是一种特殊的弱一致性:系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问都能够获取到最新的值。同时,在没有发生故障的前提下,数据达到一致状态的时间延迟,取决于网络延迟,系统负载和数据复制方案设计等因素。
在实际工程实践中,最终一致性存在以下五类主要变种:
•因果一致性
因果一致性是指,如果进程A在更新完某个数据项后通知了进程B,那么进程B之后对该数据项的访问都应该能够获取到进程A更新后的最新值,并且如果进程B要对该数据项进行更新操作的话,务必基于进程A更新后的最新值,即不能发生丢失更新情况。与此同时,与进程A无因果关系的进程C的数据访问则没有这样的限制。
•读己之所写
读己之所写是指,进程A更新一个数据项之后,它自己总是能够访问到更新过的最新值,而不会看到旧值。也就是说,对于单个数据获取者而言,其读取到的数据一定不会比自己上次写入的值旧。因此,读己之所写也可以看作是一种特殊的因果一致性。
•会话一致性
会话一致性将对系统数据的访问过程框定在了一个会话当中:系统能保证在同一个有效的会话中实现“读己之所写”的一致性,也就是说,执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值。
•单调读一致性
单调读一致性是指如果一个进程从系统中读取出一个数据项的某个值后,那么系统对于该进程后续的任何数据访问都不应该返回更旧的值。
•单调写一致性
单调写一致性是指,一个系统需要能够保证来自同一个进程的写操作被顺序地执行。
以上就是最终一致性的五类常见的变种,在时间系统实践中,可以将其中的若干个变种互相结合起来,以构建一个具有最终一致性的分布式系统。事实上,可以将其中的若干个变种相互结合起来,以构建一个具有最终一致性特性的分布式系统。事实上,最终一致性并不是只有那些大型分布式系统才设计的特性,许多现代的关系型数据库都采用了最终一致性模型。在现代关系型数据库中,大多都会采用同步和异步方式来实现主备数据复制技术。在同步方式中,数据的复制过程通常是更新事务的一部分,因此在事务完成后,主备数据库的数据就会达到一致。而在异步方式中,备库的更新往往存在延时,这取决于事务日志在主备数据库之间传输的时间长短,如果传输时间过长或者甚至在日志传输过程中出现异常导致无法及时将事务应用到备库上,那么很显然,从备库中读取的的数据将是旧的,因此就出现了不一致的情况。当然,无论是采用多次重试还是认为数据订正,关系型数据库还是能搞保证最终数据达到一致——这就是系统提供最终一致性保证的经典案例。
总的来说,BASE理论面向的是大型高可用可扩展的分布式系统,和传统事务的ACID特性是相反的,它完全不同于ACID的强一致性模型,而是提出通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,ACID特性与BASE理论往往又会结合在一起使用。