目标
了解基础理论,Quorum ,CAP, BASE, PACELC, BASE, 一致性理论
算法:Paxos, Raft
分区
分类:水平分区,垂直分区
水平分区算法
1、范围分区
通过id范围对数据进行分区
优点查询范围简单,缺点容易分布不均
2、哈希分区:缺点
通过将键进行哈希运算,根据计算的值得到分区
优点:分布均匀,缺点:当增加或删除节点时,由于每个节点都需要一个对应的哈希值,需要改变哈希函数,将所有数据重新映射
3、一致性哈希
将哈希函数的输出值组织成抽象的圆环,然后将节点映射到圆环上,根据哈希值将数据映射到圆环上,数据存储在顺时针遇到的第一个节点上
相比于哈希分区,当添加删除节点时,只会影响部分数据;
但是容易发生数据分布不均匀,且当删除节点时,会导致大量数据转移到下一个节点,造成负载倾斜
解决方法:引入虚拟节点,一个节点对应的多个虚拟节点,并且性能高的机器可以映射更多个虚拟节点,承担更多的负载
分区的挑战
1、联合查询效率低,可能需要访问多个节点的数据
2、实现分布式事务比较困难
复制
复制的好处:减小RTT(有多个节点,往返更快),增强数据的安全性,增强吞吐量
单主复制
一个主节点
分为同步复制、异步复制、半同步辅助(一个节点同步,其他异步)
多主复制
多个主节点
由于多个节点执行写操作,在网络延迟情况下容易发生数据不一致问题
解决方法:
1、客户端解决冲突,将冲突数据返回给客户端,客户端自行更新(如淘宝的购物车)
2、最后写入胜利(LWW),给写入请求加上时间戳,发生冲突时选择最新的时间戳数据
3、因果关系跟踪:先发帖再回帖
无主复制
所以节点都写,对故障的容错更高,但是导致更多的冲突
基于Quorum的数据冗余机制解决
Quorum是保证数据冗余和最终一致性的算法
当共N个节点时,要求至少W个节点写入成功,并同时从R个节点读取数据,只要W + R > N 且 W > N /2,则读取值至少包含最新的值
证明:如果至少W个节点写入成功则至多N - W个节点不成功,则R > N-W时,至少能得到一个最新的值
W > N /2是为了保证串行化修改,不让一个节点同时执行两个不同的命令
W越大R越小读性能越好,W越小R越大写的性能越好
CAP/PACELC/BASE
CAP
C:一致性,A:可用性,P:分区容错性
CAP定理:对于一个分布式读写存储系统,只能同时满足两项
由于网络故障是必然发生的基本上选择CP/AP,要在一致性和可用性中取舍
不足:CAP定理忽略了网络延迟,而网络延迟是时刻存在的,而网络分区不会一致存在
PACELC
如果存在网络分区(P)则在可用性(A)和一致性(C)间取舍,否则(E)在延迟(L)和一致性(C)间取舍
因为没有网络分区,而肯定有延迟存在,这是如果直接返回则选择延迟,出现数据不一致情况;
如果不返回则降低了可用性,保证了一致性
因此大多数系统选择AP/EL,PC/EC
BASE
基本可用 + 软状态 + 最终一致性
为了高可用放弃强一致性,选择最终一致性
最终一致性:可能会有网络分区/延迟,数据没有及时同步,但仍然允许继续读写,在最终的某个时刻,系统保证所有副本都是同步的
一致性模型
一致性从强到弱的模型
线性一致性
非严格定义:线性一致性的系统要像单一节点一样工作,并且所有操作都是原子的
严格定义:给定一个执行历史,执行历史可以拓展为多个顺序历史,只要有一个合法的顺序历史,那么就是线性唯一的
注:并发操作即有重叠的操作,可以任意顺序排列
约束条件:顺序记录要和全局时钟的顺序一致
代价:全局时钟
顺序一致性
顺序一致性允许不同客户端间的操作改变先后顺序
和线性一致性的主要区别:没有全局时间的限制,只关注局部的顺序
因果一致性
体现了一种A在B之前的因果关系
最终一致性
在最终时刻系统到达稳定的状态,即在最终状态,系统不执行写操作,读操作结果相同
Paxos
Basic-Paxos只决议出一个共识的值,之后都继续使用这个提案值
主要包括两个阶段
一:
-
提议者向接收者发送提案编号Prepare
-
接收者收到后判断,如果Prepare中的提案编号大于之前接收的所有提案编号则Promise响应
特别注意:如果接收者之前接受了某个提案,那么响应还会将上一次提案的编号和值一起发送(一直用一个提案值)
二:
-
提议者像接收者发起Accept()请求,带上提案值和提案编号,如果之前收到的Promise()中有提案值,则使用提案编号最大的提案值,否则自己决定提案值
-
接收者收到Accept()后检查提案编号,如果没有比该编号更大的提案,则接受该提案并保存
活锁问题:
如果提议者A在1a阶段发送提案给接收者,在接收者Accept之前,提议者B又在1a阶段发送提案给接收者
那么因为第二个提案编号大于第一个提案编号,接收者无法accept第一个提案
如果在接收到第二个提案后。有没有Accept,提议者A又发送新的提案,循环下去,则导致接收者一直在决定提案编号的过程中
解决方法:引入随机超时,当某个提议者发现提案没有被成功接收,则等待一个随机超时时间,让出机会
实验:用Go语言实现Basic-Paxos算法
Raft
raft的三个状态:领导者、跟随者、候选者
raft算法选出领导者后进入一个新的任期,任期分为:选举过程 + 正常运行过程
领导者选举
节点先处于候选者状态,向自己和其他节点索要选票,当受到超过半数的选票后成为领导者,或收到领导者的心跳包(空的appendEntries)时成为追随者,如果经过超时时间两种情况都没发生则开始新一轮的选举。
每个节点在同一任期只能投一次票,然后拒绝其他候选者的请求
注:超时时间是为了避免活锁,超过一半的选票是为了只有一个领导者
日志复制
日志的每个条目包括:索引、任期号、命令
raft通过索引和任期号唯一标识一条日志记录,如果一条日志记录被存储在半数节点中则认为该记录已提交
raft通过appendEntries来复制日志和 心跳包共用一个RPC,但发送心跳消息时不包含日志
raft算法通过检查最新日志消息的前一条记录的索引和任期是否一致,如果一致则追加命令,满足安全性
领导者更替
候选者在索要投票时,加上自己最后一条日志的任期和索引
收到投票的节点要比较谁的任期最新,然后比较谁的日志最完整,如果不如自己则拒绝投票
这样确保领导者在半数节点中拥有最完整的日志
日志延迟提交
延迟提交原因:如果超过半数节点保存日志就提交,那么后续新的领导者可能覆盖前面提交的日志,并提交,造成重复提交问题
因此提交日志规则:
1、日志存储在半数节点上
2、领导者必须看到半数节点山保存一条自己任期内的日志,这样新的领导者也有着要提交的日志
因此领导者在提交最新任期的日志时,也会顺带提交之前任期的日志
可是还有问题:如果最新的领导者(之前是3,现在是5)覆盖了之前的日志,此时没有新命令到来(日志无法到5),则无法提交之前日志,因此raft引入空日志,只有任期和索引,在更换领导者时立刻向自己本地追加空日志,不会阻塞查询(只有提交的日志才能返回)
清除不一样的日志
因为领导者必须保证跟随者的日志和自己的日志保持一致
因此对于少的日志会通过appendEntris补齐,对于多的日志会通过nextIndex[I]清除
nextIndex指向领导者的最后一条日志的下一条索引,appendEntris会包含前一条日志的任期和索引,当不一样时会递减,直到匹配
对于少的日志找到匹配的位置后补齐,对于多出的日志找到匹配的位置后删除后续日志然后补齐
处理旧领导者
由于每个RPC请求都会包含发送方的任期,因此旧的领导者即使收到客户端的请求,发送给其他节点,节点收到请求后发现对方任期太旧,然后拒绝请求,将最新任期发送给旧领导者,旧的领导者收到后更新自己的任期,然后将自己转为跟随者
客户端协议
客户端发送命令给任意一台服务器,服务器告知客户端领导者,然后客户端和领导者通信
由于可能导致命令重复执行,因此客户端发送请求时带上一个唯一的id,领导者在执行命令前先检查日志中id是否存在,不存在才会执行
存在则直接返回命令的响应
重复执行命令:领导者执行完命令返回之前下线,新的领导者又接收到命令
实现线性一致性读
1、领导者处理
当新的领导者的空日志提交后,领导者的提交Index至少和其他日志一样大
然后领导者将自己的提交Index 赋值给 readIndex
领导者收到读请求后,先发送心跳,确认自己是集群的领导者,然后等待状态机应用命令到readIndex
最后领导者执行读请求,返回给客户端
2、跟随者处理
跟随着先向领导者询问最新的readIndex,然后跟随着等待状态机应用命令,最后执行读请求,返回给客户端