分布式系统概要
昨天晚上看到了一本很有用的小册子,大约60多页,名字叫做Distributed systems for fun and profit,内容涉及了分布式系统的方方面面:有基本的分布式系统的概念,有复杂的分布式一致性协议,有关于分布式系统的扩展性,可用性,可靠性,高吞吐,低延迟的讨论。不出意外的话,准备用两天的时间(打脸了,两天搞不定啊,东西太多了),把它的全部内容付诸于这篇博客中。事实上,只要在搜索随便输入那本书的名字,就可以再网上搜索出一大堆的个人理解,但是最喜欢第一手资料的我还是喜欢读原文,喜欢写总结。
声明:
不喜欢完整的翻译原文,总感觉全部的翻译之后总感觉啥都没说。边写边思考再边总结,常常能获得质的提升,但是这无法避免会出现由于个人知识水平或者理解能力的欠缺引起的疏漏或者错误,还请谅解。不变的一点是最权威的参考是论文,次权威的参考是原文。本文只希望能够以个人这几年来对分布式系统的浅薄认识能对原文进行一些Chinese类型的直观理解,沉淀自己,方便他人。
当前完成的部分
[1] 分布式系统概要(https://blog.csdn.net/lpstudy/article/details/83685997)
[2] 分布式系统上下层概念抽象(https://blog.csdn.net/lpstudy/article/details/83688141)
还待做的有3,4,5。
1. 总体概览
全文分为五大章节,分别涵盖了分布式系统的基本概念,分布式系统的一致性协议,分布式系统的时序性,副本的强一致性和副本的弱一致性,基本上各个章节都穿插了分布式系统的多维特征之间的权衡:可靠性,可用性,故障容错,一致性,低延迟,高吞吐。话不多说,开始讨论。
分布式系统编程本质上都是在处理两个公理
带来的影响
- 消息在系统中以光速传播
- 独立的事物,其故障的发生也是独立的
这两句话实际上陈述了分布式系统的基本事实:延迟无法避免,因为消息传输的速度是有限的,虽然是光速,但依然会有latency(其实从网络的角度,不仅有传输时延,还有发送时延);故障的发生是一个常态事件,因为分布式系统的集群规模很大,各种各样类型的设备都会可能由于掉电,老化,以及软件层面的bug发生故障,这是一个常态事件,是无法避免的。
为了更好的理解上面的内容,我们来看两个假设:
- 假设1:消息以无穷大的速度传统,也就是没有时延
我们就可以随便采用基于consensus的强一致性的协议(例如paxos,raft等),只要一个请求能够被大部分server响应,则成功。传统意义上来说,强一致性协议的代价较大(目前paxos需要两轮的RTT),极大影响系统可用性,而无时延假设恰恰使得我们的系统可用性有保证; - 假设2:系统永远不会有故障
我们可以采用要求servers全响应语义的一致性协议(例如2PC),只有在所有的server必须全部响应后,才算做客户端请求成功,然而实际系统中各式各样的故障,使得这种要求很难满足,可用性很难保证。其实上述两个假设,恰好对应于CAP理论中的P和A,我们知道CAP无法全部满足,但是对于(a)假设,我们可以构造满足CP的系统;对于(b)假设,我们可以构造满足CA的系统,关于CAP的细节,将会在后面的第二章分布式系统的一致性协议中具体阐述。
上述的两个限制条件使得我们在分布式系统的设计中,必须去协调distance,latency以及consistence之间的关系,以设计出满足现实需求的系统
各章节简述
- 分布式系统基本概念
本节讲述 - 分布式系统数据一致性
- 时序性
- 副本的强一致性
- 副本的弱一致性
2. 分布式系统基本概念
什么叫作分布式系统?从宏观上来看,它本质上是使用多机来解决单机的问题。
Distributed programming is the art of solving the same problem that you can solve on a single computer using multiple computers.
2.1 分布式系统的基本任务
在Apache Spark的那篇论文的通用性中,我们说到分布式系统任何
类型的计算任务本质上都可以归结为两个部分,而传统的MapReduce语义恰好能够完美匹配这两个语义。
- Local Computation <—> Map
- Exchange data among different nodes <—> Reduce
同理,你不仅要问,对于一个系统来说,需要满足哪种最基本的tasks?
- Storage
- Computation
仔细思考,确实如此。所有的系统本质上都是在完成两种任务,数据存储和数据转换。例如数据库中的数据是存储,而查询语言是计算,HDFS是纯存储,Spark是纯计算。分布式系统的设计目标就是要使它从外面来看,更像是一台机器,而不是多台机器连接的组合。
2.2 为何分布式?
假如你有一台magical machine,它有足够多的资源,那么你完全不需要一个分布式系统。但是事实上,一台机器的资源总是有限的。在过去的时代,有不少企业一直在单机性能的道路上奔走,以求打造能处理更大任务的强单机系统,但是最后都宣告失败,因为随着单机性能越强,付出等量的代价带来的收益比越小。分布式系统成功的历史进程告诉我们,用大量廉价的机器打造的系统,可以获得远超过使用相同代价构造的单机系统。其实,从CPU的历史进程与之有很多相似之处,过去的CPU一直在追求单核CPU超高的频率,核数并不多(我记得2008年,很多cpu才2核心),但是频率提升到4GHZ之后,发现再向上提升频率,现有的晶体管的设计就很难满足了,因此现在为了提升单机性能,总是以量取胜,虽然单核频率并不高,但是核数多啊,包括目前的各种大型机的设计,动辄数万个核心。
让我们来看看一个证据来说明随着系统规模的扩大,高性能机器的收益越来越小。
2.3 分布式系统的设计目标
2.3.1. 可扩展性
is the ability of a system, network, or process, to handle a growing amount of work in a capable manner or its ability to be enlarged to accommodate that growth.
分布式系统从设计之初就需要考虑以后的扩展,一个好的分布式系统应该支持线性扩容的原理。也就是说,最好能够做到任意的增加机器,且增加机器就可以增加系统的对外服务能力,从而可以应对未来任何的业务增长需要。
扩容的3个点:
- 节点可伸缩
也就是说,随意的增加节点,能够线性增加系统的吞吐,且不会增加额外的时延; - 跨地区可伸缩
使用多个数据中心来服务外部用户的访问,跨数据中心的访问; - 管理可伸缩
节点等扩容不应该增加系统的管理和维护成本。
2.3.2 高性能
is characterized by the amount of useful work accomplished by a computer system compared to the time and resources used.
当提到高性能
一词,首先想到的是如下几点相关的属性:
- 低延迟
- 高吞吐
- 低资源消耗
我们知道这几个属性有很明显的tradeoff,为了高吞吐,很多系统设计成batch processing,这样带来的代价就是单个work的延迟会比较大。
2.3.3 高可用性
the proportion of time a system is in a functioning condition. If a user cannot access the system, it is said to be unavailable.
分布式系统相比於单机系统,具有更高的可用性。这是因为首先故障是无法避免的,对於单机系统来说,如果发生故障,那么它要不能够正常处理,要不就crash;而对于分布式系统来说,可以在不可靠性的硬件之上,构建一个能够容错的可靠分布式系统。那同样的问题来了,如何度量一个分布式系统的可用性,它与哪些属性有关?因此,我们有如下可用性公式:Availability = uptime / (uptime + downtime)
可用性从本质上来说是与故障容错息息相关的,所谓的不可用一般是指故障不可恢复,从而导致服务不可用。因此,从公式来说,一个系统的可用性就它可用的时间的比率,也就是上面的uptime的比率。通过上面的公式,我们可以给出传说中的4个9,或者6个9的可用性对应的实际存活时间。
我们可以看到,对于6个9来说,每年只能最多故障31s,那也就是每天不到0.1s的时间。不可否认,故障不是导致不可用的唯一因素,例如依赖的服务关闭了等同样会引起系统不可用。如果采用这种综合考虑的办法,那不确定性因素会有很多,因此,现在系统一般都会对已知的故障做专门的容错处理。
2.3.4 容错性
ability of a system to behave in a well-defined manner once faults occur
故障容错与上面的可用性息息相关,必须在设计之初通过特定的算法作专门的处理。
2.3.5 一致性
2.4 达成目标的痛点
还记得前面说的分布式系统的两个公理不? 光速限制以及故障事件的独立性。我们这里将这两个公理
进一步展开为了分布式系统的两个物理要素:
- 节点数目 (对应于分布式系统的存储规模和计算能力)
- 节点距离 (对应于分布式系统的信息传输延迟)
带有这些限制,我们的系统会
- 节点数目的增加会增加系统故障的可能性 (对应于系统的可用性降低和维护成本的提升)
- 节点数目的增加会增加系统的通信代价 (对应于系统的性能降低)
- 跨地区的节点数目的增加会增加系统的传输时延 (对应于系统的性能降低)
这些具体限制,会进一步影响我们进行实际系统设计的选择。那有什么章法可循不?我们的答案是有。
2.5 模型和抽象
说实话,这两个词一出来,就感觉这篇文章的逼格提升了一个level。鉴于自身中文解释的局限性,直接贴出别人的定义。
什么叫抽象?
Abstractions make things more manageable by removing real-world aspects that are not relevant to solving a problem.
什么叫模型?
Models describe the key properties of a distributed system in a precise manner.
抽象的本质是剥离真实世界的无关属性,显示问题的本源;模型是用于将实际问题对应到抽象的概念中。举个例子来说:为了举例,还顺带复习了一把欧拉回路问题
当时东普鲁士科尼斯堡(今日俄罗斯加里宁格勒)市区跨普列戈利亚河两岸,河中心有两个小岛。小岛与河的两岸有七条桥连接。在所有桥都只能走一遍的前提下,如何才能把这个地方所有的桥都走遍? ——Wikipedia
上面的就是著名的科尼斯堡七桥
问题,针对这个问题,欧拉首先将其抽象
为一个图,他利用抽象
将真实世界的无关属性剥离开来,例如桥是边,两个桥之间的路是顶点;其次,抽象
之后现实世界的问题就成为了图问题
,将这个图问题进行建模,转换成的模型
变成:问在这个图中,是否有一个路径能够覆盖全部的边,且每个边不能重复?不知道说明白了没有,简单说来抽象是剥离现实世界,模型对现实世界的问题进行建模,以映射到抽象的model上。
对于一个分布式系统来说,抽象使其更容易被人所理解,但是现实情况下,对外暴露一些细节往往可以做专门的优化,从而提升性能。在后面的介绍中,会涵盖几种经典的模型
- 系统模型(同步或者异步)
- 故障模型 (crash-fail, partitions网络分区, Byzantine拜占庭故障)
- 一致性模型 (strong强一致性,eventual最终一致性)
2.6 设计技巧:partition和replication
这个地方主要讲述数据的散布方式,因为它直接决定了计算如何定位数据以及操作数据。
对于一个数据集,通常有两种基本的技术
- 将其划分成一个个小的分片(partition),这样可利用分布式的集群并行运算能力。
- 将其copy多份以处理系统的故障,或者降低client和server端的延迟(使用local replication服务)
2.6.1 Partition
数据划分的关键在于如何划分数据,也就是数据划分的算法,一般是通过hash(key)的方式划分到不同的node上,这需要一大章节来介绍,本文忽略。
数据划分带来的收益:
- 提升系统可用性,减少故障域,因为分片很小,一个机器的故障只会导致较少的数据不可用;
- 提升系统性能,减少数据处理量,因为每个机器处理的数据变小了。
注意:上面的第1点从其他的角度来看不一定完全正确,分片越小,则散布越开。这样的话,任意故障多个机器,都会导致一个分片的全部副本全丢失,根据copysets的paper计算的概率,这样发生数据丢失的概率会更高。
2.6.2 Replication
副本简直是处理故障恢复的的万能钥匙。
数据副本的收益:
- 提升系统可用性,需要挂更多的节点才会导致数据丢失
- 提升系统性能,多个副本可以同时处理或者交给更快的机器处理
分布式系统采用副本可以获得可扩展性,高性能,可用性,容错性
- 害怕数据不可用,采用副本吧,多副本能确保数据由于故障丢失的概率大大降低;
- 计算太慢,采用副本吧,将计算散布到多台机器上;
- I/O太慢,采用副本吧,将数据cache到local机器上,可以极大的提升吞吐。
副本带来一个显而易见的问题是数据一致性的问题,因为系统需要维持同一份数据的多份copy,并确保他们的同步。如果希望自己的分布式系统使用副本就像没有使用副本那样,那就采用强一致性协议(在后面的第4章),然而这通常意味着更高的cost;弱一致性协议(在后面的第5章)能够极大的提高系统的吞吐以及更低的延迟,从而在分布式系统中得到更大的应用。