分布式理论--现代架构基石| 青训营笔记

106 阅读11分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天

分布式概述

什么是分布式

官方说法:

分布式系统是计算机程序的集合,这些程序利用跨多个独立计算节点的计算资源来实现共同的目标,可以分为分布式计算,分布式存储,分布式数据库等。

俺的拙见:

分布式就是把一个很大很大很大的程序在多个计算机服务器上运行,包括计算,存储,数据库等。

Why-How-What

使用者:

Why(为什么用分布式)

1,数据爆炸,对存储和计算有大规模运用的诉求。

2,成本低,构建在廉价服务器之上。

How(怎么用分布式)

1,分布式框架。

2,成熟的分布式系统。

What(怎么用好分布式)

1,理清规模,负载,一致性要求等。

2,明确稳定性要求,制定技术方案。

学习者:

Why(为什么用分布式)

1,后端开发必备技能

2,帮助理解后台服务器之间协作的机理。

How(怎么用分布式)

1,掌握分布式理论

2,了解一致性协议

What(具体怎么学习分布式)

1,把要点深入展开,针对难点搜索互联网资料进行了解。

2,将所学知识运用于实践。

常见的分布式系统

分布式存储

1,Google File Systeam

2, Ceph

3, Hadoop HDFS

4, Zookeeper

分布式数据库

1, Google Spanner

2, TiDB

3, HBase

4, MongoDB

分布式计算

1, Hadoop

2, Spark

3, YARN

系统模型

故障模型

根据对处理故障的难度划分为六种(由高到低):

1,Byzantine failure:

节点可以任意篡改发送给其他节点的数据。

2,Authentication Detectable byzantine failure(ADB):

Byzantine failure的特例;节点可以篡改数据,但不能伪造其他节点的数据。比如说内存发生了错误或者磁盘发生了错误等等。

3,Performance failure:

节点未在特定时间段内收到数据,即时间太早或太晚。(有时候不在规定时间内完成处理不如死掉不处理,因为分布式系统中能够处理的节点很多,没必要纠结于某一个节点。)

4,Omission failure:

节点收到数据的时间无限晚,即收不到数据

5,Crash failure:

在Omission failure的基础上,增加了节点停止响应的假设,即持续性地Omission failure。

6,Fail-stop failure:

在Crash failure的基础上增加了错误可检测的假设。

从四个状态来看这些故障: 1,正确性 2,时间 3,状态 4,原因

Fail-stop既知道状态又知道原因;Crash只知道状态不知道原因; Omission和Performance是一种未决的一种情况,Omission等待的时间无限长;Performance是一种故障和非故障的叠加态;Byzantine是正确性未知的故障,最难处理。

实际故障: image.png

拜占庭将军问题

引入:

两将军问题( Two Generals' Problem ) :

两支军队的将军只能派信使穿越敌方领土互相通信,以此约定进攻时间。该问题希望求解如何在两名将军派出的任何信使都可能迷路的情况下,就进攻时间达成共识。--来自wikipedia

结论是,两将军问题是被证实无解的电脑通信问题,两支军队理论上永远无法达成共识。

有点懵圈?这么简单还无解?不着急,我们来捋一捋:

情形一:

将军A:你去告诉对面,明早9点发起进攻。
小兵a:收到。
小兵a跋山涉水,历尽千辛万苦,最终迷路了。


第二天:
将军B:啥?

由于将军A的信息未能传递给将军B,将军B并未出兵。最终双方未能达成共识。

情形二:

为了避免上次的教训,双方决定在接收到对方的进攻信号后派人给对方回复,收到确认信息后再出兵。

将军A:你去告诉对面,明早9点发起进攻。
小兵a:收到。
小兵a跋山涉水,历尽千辛万苦,将消息带给了将军B。
将军B:收到,并派出小兵b给将军A传话以示确认。
小兵b跋山涉水,历尽千辛万苦,最终迷路了。

第二天:
将军A:啥?

由于将军B的确认信息未能传递给将军A,将军A并未出兵。最终双方未能达成共识。

情形三:

为了避免上次的教训,将军A决定在收到将军B的回复后再向将军B发送一条确认收到的信息,以便双方达成共识。(也就是大家熟悉的TCP三次握手的极简版本)

将军A:你去告诉对面,明早9点发起进攻。
小兵a:收到。
小兵a跋山涉水,历尽千辛万苦,将消息带给了将军B。
将军B:收到,并派出小兵b给将军A传话以示确认。
小兵b跋山涉水,历尽千辛万苦,将消息带给了将军Aa
将军A:收到,并再次派出小兵a,让他告诉将军B确实收到了作战信息。
小兵a跋山涉水,历尽千辛万苦,最终迷路了。

第二天:
将军B:啥?

由于将军B未能收到将军A的确认收到信息,所以将军B并未出兵,最终双方未能达成共识。

经过上面的分析我们可以推出,即使派出更多的小兵进行确认,依然有信息丢失(小兵迷路)的可能,所以理论上讲两支军队永远无法达成共识。这个概率只会无限趋近于1。

那么解决拜占庭将军的更好地方式是什么呢?

方案一:同时发送N个小兵,任何一个达到对方军队,都算成功。

方案二:设置超时时间,派出小兵传递信息后未在一定的时间内收到确认信息,则加派小兵。

共识和消息传递的不同:即使保证了消息传递成功,也不能保证双方一定能够达成共识。

TCP三次握手是在两个方向确认包的序列号,增加了超时重试机制,是两将军问题的一个工程解,但并不是一个理论解

关于TCP三次握手的思考:

image.png

1,为啥三次握手而不是两次握手或者四次?

首先是两次握手的情况:

2.1 A 发送同步信号SYN + A‘s Initial sequence number
2.2 B 发送同步信号SYN + B's Initial sequence number + B's ACK sequence number

存在的问题是,B无法知道A是否已经接收到了自己的同步信号,若这个同步信号丢失了,A和B就B的初始序列号将无法达成一致。(类似于上面所分析的情形二)

然后是四次握手的情况:

4.1 A 发送同步信号SYN + A's Initial sequence number
4.2 B 确认收到A的同步信号,并记录 A's ISN 到本地,命名 B's ACK sequence number
4.3 B 发送同步信号SYN + B's Initial sequence number
4.4 A 确认收到B的同步信号,并记录 B's ISN 到本地,命名 A's ACK sequence number

很显然1.2和1.3 这两个步骤可以合并,只需要三次握手,可以提高连接的速度与效率.

2,在四次挥手的过程中,如果FIN报文丢失,会发生什么?

image.png

第一次挥手时(客户端发送的FIN报文丢失):

当客户端(关闭主动方)调用close函数后,向服务端发送FIN报文,试图与其断开连接,并进入到FIN_WAIT_1状态。此时若FIN报文丢失,则服务端不会收到FIN报文,也不会向客户端发送ACK报文,这时就会触发超时重传机制,客户端会重传FIN报文,重发次数由tcp_orphan_retries参数控制。若客户端重传FIN报文次数超过tcp_orphan_retries次数后,就不会再发送FIN报文,直接进入close状态。

第三次挥手时(服务端发送的FIN报文丢失):

当客户端收到第二次挥手,即收到服务端发送的ACK报文后,客户端会处于FIN_WAIT2状态。对于close函数关闭的连接,由于无法再发送和接收数据,所以FIN_WAIT2状态不会持续太久,而tcp_fin_timeout控制了这个状态下连接的持续时长,默认值60s。服务端在发送了FIN报文后,进入LAST_ACK状态,等待客户端发送ACK报文来确认连接关闭。如果一直没有收到ACK报文,服务端会重发FIN报文,重发次数依然由tcp_orphan_retries参数控制,与客户端重发FIN报文的重传次数控制方式一样。

更加普适的拜占庭将军问题

引入:

拜占庭将军考虑更加普适的场景:例如3个将军ABC互相传递消息,消息可能丢失,也可能被篡改,当有一个将军是“叛徒”( 即出现拜占庭故障)时,整个系统无法达成一致如果没有“叛徒,无论各自观察到怎样的敌情,总能达成一致的行动。

更懵圈了?我们再来捋一捋:

假设三个将军ABC都是老实巴交的三体星人,不会对对方有所隐瞒或者篡改信息。并且通过观察敌情并独立投票,采取少数服从多数的方式来确定是否发起进攻,那么:

假设将军A,C判断应该发起进攻,将军B判断应该坚守高地。根据少数服从多数,三个将军都会发起进攻。

假设将军A,C判断应该坚守高地,将军B判断应该发起进攻。根据少数服从多数,三个将军都会坚守高地。

也就是说,由于可以进行的选项只有两个,大家又都是老实人,所以当人数为奇数的时候大家总能达成一致。

可惜社会太现实,现在我们引入狡猾的地球人,他们可能会隐藏或者篡改自己的真实意图。

假设将军A决定发起进攻,将军B决定坚守高地。将军C作为狡猾的地球人,向将军A发送了进攻信息,向将军B发送了撤退信息。那么将军A得到的投票结果是两票进攻一票撤退,A决定进攻;将军B得到的投票结果是两票撤退一票进攻,B决定坚守高地。

从而产生了分歧,未能达成一致。

为了应对狡猾的地球人,我们再引入一个公证人将军D,其只作为消息分发中枢,不参与投票,约定若未收到消息便执行撤退。并执行两轮投票,第一轮投票结果由D传递给A,B,C三位将军,第二轮投票由A,B,C三位将军自己执行。

当D为狡猾的地球人,A,B,C为老实巴交的三体星人的时候:

第二轮投票就退化成了我们最开始讨论的情况,老实巴交的三体星人最终总会达成共识。

当D为老实巴交的三体星人,而A,B,C中出了一位狡猾的地球人的时候:

第一轮投票A,B,C一致接收到D的命令,决定发起进攻。而第二轮三位将军独立投票时狡猾的地球人C故技重施,向将军A发送了进攻的信息,向将军B发送了撤退的信息。然而由于有了第一轮的投票,A得到的投票结果全是进攻,A决定进攻;B得到的投票结果是两票进攻一票撤退,B决定进攻;此时无论C决定进攻还是撤退,根据少数服从多数原则,他必须进攻。

从而大家达成了一致。

ed3af9a181565ca66d8baf96f39d9c0.jpg

进而能够证明,当有3m+1个将军,其中m个狡猾的地球人时,可以增加m轮协商,最终达成一致。

共识和一致性

之前已经介绍过了共识的问题,下面介绍一致性的概念。

最终一致性

客户端A读到x=0,当客户端C正在写入时客户端A和B可能读到0或者1。但是当C写入完成后,A和B最终能读到一致的数据我们称这样的一致性为Eventuallyconsistent ( 最终一致性)

image.png

线性一致性

当客户端A读到更新的版本x=1后,及时将消息同步给其他客户端,这样其他客户端立即能获取到x=1。我们称这样的一致性为Linearizability ( 线性一致性)

image.png

理论基础

CAP理论

分布式事务

共识协议

分布式实践