分布式算法的发展历史是一个由理论探索到技术应用的过程,从Leslie Lamport在20世纪70年代奠定分布式一致性研究的基础开始,随着需求推动架构的演进变迁,分布式计算、存储和资源管理等方面的技术也得到了长足发展。
上世纪70年代至今不过短短数十年,分布式技术的研究看起来还非常“年轻”。但从另一个角度来想,分布式系统的运行就是多个节点共同完成某项任务的过程,而群体协作似乎天然存在于人类社会之中,从个体互助到国家社会化分工,可能我们并未发明任何新的分布式算法,只是重新用了计算机来承载它。
通过生活的经验来理解分布式系统的运转,以最简单的二人结对工作来分析协作方式,譬如甲乙两人稻谷收成后需要舂米,协作或有三种模式:
- 甲放稻谷,乙来踩碓。重点是按职能分工,对应垂直拆分的概念,例如微服务架构。
- 稻谷分成两份,甲乙各舂一堆。重点是分治扩展,对应水平拆分的概念,例如分库分表。
- 一直由甲舂米,其生病后由乙顶上。重点是业务连续性,对应容灾高可用的概念,例如主备切换。
正常情况下,每种协作方式都能将稻谷加工成大米,前提是参与方清楚地知道自己该干什么,否则简单的工作也会搅成一团乱麻,例如:
- 甲乙都在放稻谷,没有人去踩碓,工作无法推进。
- 假设按稻谷和精米进出计价,但甲乙都时不时去对方那抓一把,造成双方账目不平。
- 甲生病告假,乙不知该自己当班,导致工作停摆,或贸然顶替影响甲正常工作。
由此可见,要解决的基本问题是节点的认知问题,即对环境与个中节点状态的判断。一般来说,分布式系统让多台设备像单机一样完成某项工作,完全垂直拆分更接近于两个系统之间的交互问题,但无论如何,身份认知都是一切个体行为可稳定预期的基础。
决策是由节点根据输入信息产生的,关键在于如何让集群中的每个节点都产生一致的整体认知,这就是分布式共识问题。社会的正常运转也有赖于个体的共同认知,否则纠纷冲突不断,社会秩序也将土崩瓦解。从表面上看,人与人之间的秩序来源于对某种权威确立规则的服从,例如上级指派的分工和法院裁定的判决。但更深层次地,权威实际也是由共识赋予的,自封的皇帝下不了圣旨,前朝的剑斩不了当朝的官,旧版本的配置不可兼容,这些都有相通的道理。
那么,共识的本质是什么?从某种意义上,共识的本质是合群,只要你和周围大多数人一样,就不会被定义为异类,从而被排挤离群。人类社会是极其复杂的,有时只能唱响一种主旋律,有时允许小众文化流行,有时需要和衷共济,有时也可离群索居。分布式的世界里没有这么多弯弯绕绕,就像一个小型民主社团,永远都是少数服从多数,这是系统秩序的根本。
既然依赖于少数服从多数,分布式集群的节点数量就得是大于1的奇数,否则就可能出现均势僵持的局面。正常情况下,如果要求f个节点故障时系统还能正常运转,集群的总节点数则最少需要2f+1,数量多一些也不影响最低可用性目标,但会增加节点间的通信成本,可能影响整体效率。
人有好坏诚伪之分,如果投票的时候有人蓄意作乱,比如说跟每个人讲的话都不一样,或者干脆摆烂不发表意见,那么该如何确保这个小型民主社团能够正常决断议题,以及最终提案是符合大多数“好人”的利益的?这依然是个数量游戏,同上面正常情况下2f+1类似,我们不能直接在人群当中抓出坏分子,只能假设有f个“坏人”混了进来,需要多大的社群才能将其正常“消化”。
就像一场狼人杀游戏,你并不知道当下发言的人是什么身份,只知道狼人最多有几个,所以你要耐心地听所有人的发言,直到找到超过狼人数量的那个共同意见,那么你最少需要收集2f+1个人的观点(是否包括自己取决于你能否提供有效信息)。但正常节点也有故障概率,“好人”也可能已被误杀出局,如果同样要多容忍f个正常节点故障,这种情况下集群的节点数最少需要3f+1,这样即使已出局的f个玩家全是被误杀的,还是有足够的“好人”能共同左右局势。这实际是分布式领域经典的“拜占庭将军问题”,PBFT(Practical Byzantine Fault Tolerance,实用拜占庭容错)算法提供了一套切实可行的解决方案和理论证明。
前面的类比可能过于简化,也不甚准确,但重要的是理解分布式系统中选择“大多数”作为裁定原则的意义。除了大小比较是无可辩驳的绝对标准外,“大多数原则”也意味着交集,当一个多数群体决策某件事情时,其中至少有一位是参与过上次决策的,即使其他人因或这或那的原因丢失了部分数据,也可从中补全信息,这样既能保障数据一致性,也能保障数据完整性。
明朝的两京制在历史上是一种比较特殊的行政管理制度,从最早明太祖朱元璋定都南京,到明成祖朱棣迁往北京,最终由明英宗朱祁镇正式确立以北京为京师、南京为陪都的两京制度,经过了长时间的发展和变化。1644年,闯王李自成率领大顺军进入北京,两京之间的联络被切断,南京这套班子一时陷入两难,想开展工作提请奏章,但消息已绝得不到谕旨朱批,要是现在贸然拥立新王登基,万一皇上洪福齐天,事后清算又是诛连的大罪。
经典的CAP原理或许能与之类比,分布式系统设计中必须在一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)间做出权衡,三者不可兼得。一般来说,在广域网络环境下,分区几乎是无可避免的,就像两京联络断绝,业务往往要在CP和AP间做出取舍,如果追求一致性,那就必须等到崇祯皇帝确切的消息,哪怕在此期间所有工作都停摆,如果追求可用性,那就必须马上拥立新王,以便机构正常运转,至于事后可能出现两个皇帝的局面,那就再行商议吧。
CAP原理似乎把一致性和可用性放到了完全对立的两面,但它们其实并不是透镜下会聚的斑点,而是可以色散开的光谱,通过对一致性和可用性的再权衡,在CAP的基础上发展出来BASE原则,旨在通过牺牲强一致性来获得更高的可用性和最终一致性,它包含三个核心概念:基本可用(Basically Available)、软状态(Soft state)和最终一致性(Eventually consistent)。
最终一致性指系统各节点可能无法立即达到一致的状态,但随着时间的推移,会逐渐收敛达到完成一致,其中过程就已蕴含基本可用和软状态的理念。空谈原则难免过分陷入形而上学,前面提到的民主票选可以当作BASE的一个具体应用场景,例如当你想向社团提出某项意见表决时,不需要等待所有人的意见,只要多数人投票确定了结果,即使有部分成员当时不便与会,事后也可查看会议纪要知晓详情。
再来看决策的具体过程,为了确保一项提案得到了大多数成员的认可并最后生效,一般需要两个阶段流程,首先发起提案得到大多数成员的响应,其次发起确认让成员们执行提案。但是,如果确认阶段前又有新提案发起,就要考虑是等待上一个提案完成还是舍弃掉它,现网环境下消息可能延迟或丢失,多节点等待可能极大影响效率,同时未诀的信息需要缓存,这也增加了内存使用的不确定性,所以常规的处理方式是响应新的舍弃旧的。
当成员们都七嘴八舌自顾自地提出意见时,会议就像乱糟糟的市场拉客,看起来热闹,实际并没有做成什么买卖,因为旧提案的确认总是会被新提案的响应所冲顶,即使场上只有两个提案,也可能持续互相覆盖,形成“活锁”。解决这个问题的一种好方式是先选出一位“会议主持人”,由他来收集意见并推进议程,通过单点顺序来维持秩序,这也是为什么众多分布式算法都有“Leader”的角色和选举过程。
Leader选举和其他普通提案一样,也需要大多数成员达到共识,如何避免出现前面提到的活锁问题,需要进行特殊处理。就像封建社会立长还是立贤的争辩,往往贯彻嫡长子继承制的时候权力交接相对稳定,否则大概率会出现派系林立争斗不休的局面,这就意味着稳定的Leader选举可以依赖节点的某些可比较的不变特性,例如节点序号、日志序号,甚至IP转化的数字大小都可以作为冲突避让的标准。当然,只讲究先来后到也不是毫无办法,关键在于减少同时争抢的概率,比如Raft采用的随机等待时间,隐形地给节点编排了竞选的次序。
在电影中经常能看到这样一种场景,特警们在任务执行前需要先进行“对表”,以确保预定时间点的作战计划配合无误,生活中我们基本也只用时间来表达某件事情发生的时机,以及不同事情的先后顺序。分布式世界的天上没有太阳,不能投射在每个节点的日晷上共享时间,时钟是本地的,如何准确表达分布式系统中的“全局时序”,是实现集群节点间数据一致性的关键问题之一。就如前面提到的缺席会议的例子,如果会议纪要左一榔头右一棒槌,未参加会议的成员事后该如何从中厘清上下文,如果不能顺序阅读,可能就难以取得与其他人一致的认知。
数字随事件发生的次数递增是板上钉钉的,但单一数字无法准确表达分布式系统的时序,它更像是古代皇帝的年号,因为事件只在当前版本的环境中有效,版本更迭的冲突必然伴随旧版本未定数据的舍弃。例如,旧主节点故障下线,其中编号为n的事件还在第一阶段,只有少数节点M接收到这个提案,其中并不包括新选中的主节点,如果新主节点后续以编号n发起另一提案,前面的少数节点M该如何分辨它们。所以,在有主节点角色的分布式系统中,事件次序常以<主节点序号,提案序号>来表示,先明版本后定时序。打个比方,天启七年明熹宗朱由校驾崩,他的弟弟朱由俭继位并改元崇祯,如果新皇登基已经昭告天下,地方衙门慢一步才收到熹宗生前所发谕旨<天启,100>,那或许该搁置一旁,或再提请新皇定夺<崇祯,1>。
大部分时候我们只关注在给定集群中如何处理问题,这个情况下构成集群的节点是已知且固定的,如果要支持一个运行中的集群增删节点,该采取怎样的流程,事件次序又该如何表示。次序表示相对来说比较直观,以前述为例,需要添加集群标识,如使用<集群序号,主节点序号,提案序号>来表示特定集群节点集合下,某个主节点发起的一次提案。节点增删也将作为一项提案提出,以期在集群中达到共识,但合理性还需要流程特殊配合,如主节点在发起该提案后即不再接收新提案,以免自己后续退出留下许多手尾,另外还有旧集群的任务是否要排干,新集群接替如何平滑,更多是流程管理问题,不是数据一致问题。
前面主要是以集群内部的视角探讨分布式系统运作的过程,其中也有影响系统使用的方面。以客户端连接系统查询数据为例,就像你路过陌生村庄想打听消息,在人人都说真话的情况下,即使路上随便拦下村民询问,哪怕这位村民有些时事尚不清楚,热情点的会给你指引去往村长家的路,冷淡点的充其量给你说说旧闻,也不至于骗你。但是,如果这村里可能有f个骗子,那你就得问够2f+1个人,才能判断他们哪些说的才是真的。