系列目录
一个分布式系统最多只能同时满足
一致性(Consistency):总能保证读到的数据是最新的数据。需要保证所有节点随时更新到最新数据。
可用性(Availability):保证服务的可用,不出现失败或者超时等情况。
分区容错性(Partition tolerance):当分布式系统中节点的通信出现故障,依旧能提供具有一致性和可用性的服务。
这三项中的两项。
核心原因是故障不可避免,此时如果要保障三者中的二者,则必然会损害到第三者。
CA:保障一致性和可用性,牺牲分区容错性
此时要保证所有读取都是最新数据,而且可用,在故障的前提下,说明不存在节点通信,也就没有分区容错性。
CA就是假设网络不存在问题,一般单机或者小规模集群会采用。
AP:保障可用性和分区容错性,牺牲一致性
假设拥有最新数据的节点出现通讯故障,则必然存在部分节点的数据无法更新,若要保证这部分无法更新的节点的可用性,则无法达成一致性。
适用于对数据时效性和一致性要求不高的系统。
CP:保障一致性和分区容错性,牺牲可用性
同上,若要保证一致性,则部分无法更新的节点的可用性无法保证,需要阻塞等待网络恢复。
对于涉及金钱交易的系统,或者每次操作代价很大的系统,是非常常见的选择。
Basically Available(基本可用)
Soft state(软状态)
Eventually consistent(最终一致性)
BASE是对CAP中一致性和可用性进行权衡的结果,稍微放低一些要求,达到更好的泛用性。
让系统中的不同节点能够协调地进行作业。
在若干节点(服务器)中,需要选出一个节点来进行工作的分配与协调,被称为协作者/领导者。选出协作者的方法就是选举算法。
选择编号最大的节点作为协作者。当任何一个节点发现协作者不再响应请求时,它就发起一次选举。
向所有编号比它大的节点发送一个ELECTION消息
如果无人响应,该节点获胜并成为协作者
如果有编号比它大的节点响应,则由响应者接管选举工作,继续发送ELECTION消息。
最终胜利者(活动节点中编号最大者)将选举获胜的消息发送给所有节点,通知这些节点自己是新的协作者。
当一个以前崩溃了的节点现在恢复过来时,它将主持一次选举。如果该节点恰好是当前正在运行的节点中编号最大的节点,它将赢得此次选举,接管协作者的工作。
这样,现存节点中编号最大的节点总是取胜,故称为“欺负算法”。
Raft通过当选的领导者达成共识。使用Raft算法,系统有两个状态:选举状态、工作状态。
每个server可能会有3个身份:领导者(Leader)、候选者(Candidate)、跟随者(Follower)
选举过程
系统初始化时,所有服务器都是Follower,没有Leader。
每个服务器有一个计时器,如果超时没收到Leader发来的心跳信息,则认为Leader已经崩溃。
当发现Leader不存在时,节点会变成Candidate,发起新一轮选举。
节点会首先给自己投票,然后向集群中其他所有的节点发起请求,要求大家都给自己投票。
其他收到投票请求且还未投票的Follower节点会向发起者投票,发起者收到反馈通知后,票数增加。
当得票数超过了集群节点数量的一半,该节点晋升为Leader节点。
Leader节点会立刻向其他节点发出通知,告诉大家自己是Leader。收到通知的节点全部变为Follower,并且各自的计时器清零。
如果由于网络问题,其中一个leader崩溃之后又进入了集群,此时集群中存在两个leader。此时节点会关注leader的“任期”值,新leader的任期值较大,旧leader会退化为follower。
工作过程
client向leader发送数据。
leader向所有follower复制数据,如果失败则不断重试。
复制成功,follower向leader返回ACK。
当leader收到过半数follower发送的ACK,则向client返回成功。
leader继续未完成的同步作业。
核心思路:发生冲突时,只接受最新的提案。
client:系统外部角色,负责发起请求。
proposer:接收client请求,向集群提出propose,并在冲突发生时起到调节作用。
acceptor:集群中提议的接受者,只有大多数acceptor接受的提议才会形成决定。
learner:提议接受者,但是不影响决策,只负责备份已经接受的提案。
1a. prepare
proposer提出一个提案,提案只包含一个编号N。N对于每个proposer都是单调递增的。
1b. promise
acceptor收到提案,如果提案的编号大于他收到的所有提案的编号,则接受并返回ACK,否则拒绝。
2a. accept
如果某个提案的接受者达到了大多数,proposer会发出accept请求,请求中包含提案编号N和内容。
2b. accepted
如果acceptor在收到accept请求期间没有再收到更大的N的提案,则接受并向proposer和learner发送,否则忽略。
上述算法存在一些问题,譬如:
难以实现、效率低(需要两轮请求)、存在活锁现象(如果不断有新提案出现,则难以达成共识)。
解决方法:只设置唯一的proposer,称为leader。只有leader能提出提案。
此时就不需要两轮同步,只需要一轮提案就可以达成共识。
数据一致性问题是复制造成的,数据在不同节点之间复制,会由于各种问题导致不同节点间的数据存在差异,如复制差错、网络崩溃等。
一般而言,我们以leader/master节点为数据的原本,讨论follower/slave节点的数据一致性问题。
保证任何一次读取都是最新数据。
且任何进程锁能看到的操作顺序,都和全局时钟下的顺序一致。
leader 在保证了所有 follower 的数据和自己完全一致后才向用户确认,达成强一致性。
强一致性以外的都是弱一致性。弱一致性能够容忍过时的数据读取。
顺序一致性
任何一次读取都是最新数据。
所有进程看到的操作顺序是一致的,就算错了也是一起错。
系统可能因为各种原因无法及时达成一致,存在某些 follower 的数据滞后,这时候就出现了不一致的情况。
最终一致性保证这种不一致的状态是暂时的,整个系统会在一段时间后达成一致性。
不同的最终一致性可以在不同的局部范围之内,保证一定程度的一致性。
读写一致性
保证用户的每次成功的写操作后,读到的数据中都是写成功后的版本。
单调读
用户如果先后从不一致的不同库中读取信息,可能会出现后读取的信息版本更旧的情况。
单调读保证后读取的数据版本不会更旧。
因果一致性
让以逻辑顺序发生的事件被以正确的逻辑顺序读。
譬如,提问的出现应当在回答之前,那么读取时应当保证不会先看到回答再看到提问。
用有限的服务器存储大量数据,如何将数据均匀地存放在不同的服务器中?
普通哈希算法
假设有0、1、2三台服务器,那么可以对数据进行哈希运算之后对3取余,根据运算结果决定数据存放在哪个服务器上。
这样的算法可以保证数据分布的均匀性,但是可扩展性差。当要增加或减少服务器节点的时候,会有大量数据需要进行迁移。
服务器仍是有限的,但是数据的取不再对服务器数量取余,而是对2^32-1取余。
服务器也对2^32-1取余,使得到的结果分布在 0 ~ 2^32-1 构成的圆环上。所有的数据存放在按圆环顺时针寻找到的第一台服务器。
这样,当我们需要对服务器进行增减,就只会影响到某一部分的数据。
存在的问题:
数据偏斜:如果服务器在圆环上分布不均匀,可能会导致服务器存放数据不均匀。
雪崩现象:当一个服务器失效,它的所有数据要迁移到下一个服务器,可能会导致下一个服务器过载严重也失效,造成链式反应。
解决方法:虚拟节点
圆环上不一定只能放物理服务器,可以将每个物理服务器映射成多个虚拟服务器放在圆环上,可以有效解决上述两个问题。
一般而言,节点数量越多,发生问题的概率越小。
哈希槽是Redis采用的方法,可以看做是一致性哈希的简化和优化。
不使用普通的哈希算法和32位的哈希空间,而使用 crc16(key) mod 2^14(=16384) 简化的哈希算法,实现更加简单。
每个节点都管理数量不定一组哈希值,称为哈希槽。增删节点时,会进行哈希槽的分配,也可以手动分配哈希槽。
每个节点不是单个服务器,而是一组master-slave的集群,master负责数据的接收和处理,slave负责数据的备份。
是一个基于观察者模式设计的分布式服务管理框架,负责存储和管理数据,接受观察者的注册。当数据发生变化,则通知zookeeper上注册的观察者。
zookeeper = 文件系统 + 通知机制
其基本构成是一个leader,多个follower的集群,可以高效管理集群,提供强一致性和可用性(zookeeper,小动物管理员)
zookeeper可以单机部署,此时就是多进程模型,主要用于测试。
zookeeper所要处理的数据量很小,所以分布式部署时可以近乎看成是单机。
特性
可用性:集群只要有半数以上节点存活,就能正常服务。建议奇数台服务器,至少三台。
强一致性:每个节点都保存一份相同的数据副本
实时性:在一定时间范围内,client总能读到最新数据。
顺序执行:来自同一个client的更新请求都按照其发送顺序执行。
原子性:数据更新要么成功,要么失败
文件系统:类似于unix文件系统,由树形结构组织成的ZNode,每个节点存放1MB数据,通过路径唯一标识。
提供的服务
统一命名服务 提供分布式环境下的全局命名,便于识别。
统一配置管理 提供分布式环境下的配置文件同步,保证所有节点的配置信息一致。 可以通过将配置信息写入一个ZNode,然后其他客户端监听这个ZNode,一旦有修改则通知同步。
统一集群管理 提供实时监控节点变化。 可以将节点信息写入一个ZNode,并监听这个ZNode。
服务器端动态上下线 可以将服务器信息写入一个ZNode,并监听这个ZNode。
总之就是监听ZNode
选举机制
类似欺负算法,在所有节点中,可能有若干节点同时发起投票。此时节点会将票投给发起投票的节点中编号最大的那个。当节点收到的票数过半则直接当选。
节点分类:持久节点和短暂节点,取决于断开连接后是否删除。
是观察者模式的核心。
和raft的写数据流程基本一样。
kafka是一个分布式的基于发布/订阅模式的消息队列,主要应用于大数据实时处理
消息队列
实现消息的异步传递。提供一个消息的缓冲区,发送的消息可以先存放在缓冲区中,然后慢慢处理。
发布/订阅模式
是一种一对多的消息传递模式。相对于点对点的模式。
消息的生产者将消息发布到topic中,同时可以有多个消息的消费者订阅该消息。
消费模式有两种:topic推送数据,或者消费者从topic中拉取数据,kafka选择后者。
好处:考虑到了消费者的处理能力,不容易造成资源浪费
缺陷:消费者要维持一个常轮询来拉取数据,消耗CPU
生产者(Producer):生产消息的进程
消费者(Consumer):消费消息的进程
kafka集群(Broker):由kafka服务器构成的消息管理模块,每个Broker是一个服务器节点
主题(Topic):消息按主题分类,消费者根据需求拉取对应主题的消息
分区(Partition):将同一个Topic的消息分成不同Partition,存放在不同的服务器上,实现不同机器的负载均衡。
副本(Replication):保证可用性,同一个Partition需要有备份,分为leader和follower。
消息的生产、消费都在Leader上进行,Follower只进行备份。当Leader崩溃时,Follower会选举出一个Leader。
备份会放在不同机器上,所以副本数应当不大于机器数。
消费者组(Consumer Group):消费者可以进行分组。同一个组内的消费者不能重复消费一个分区,用于提高并发性。
偏移(offset):用于记录消费者消费到了Partition中的哪一条信息,要存放在Zookeeper(0.9版本之前)/kafka磁盘中,方便崩溃恢复之后继续工作。
kafka 通过 ack 保障数据可靠性。
当 kafka 收到生产者发来的消息后,会先进行同步,同步完成后返回对应的 ack。
ack参数
ack=0 生产者不管ack,只负责生产,基本没有可靠性。
ack=1 leader 落盘后就会返回ack,如果leader同步完成后出现故障,就会数据丢失。
ack=-1 见下方。
ack 方案
kafka 既不是全部同步后才 ack,也不是半数同步 ack,而是维护了一个ISR列表,其中存放了活跃 follower 的编号。
如果一个 follower 长时间未向 leader 同步数据,则踢出 ISR。等到 follower 恢复并且同步完成时再加回来。
leader 在同步了 ISR 中的 follower 之后,就返回 ack。
如何保证 follower 之间的数据一致性?
LEO:指每个 follower 的最大的 offset
HW(高水位):指消费者能见到的最大的offset,LSR队列中最小的LEO。
核心:follower 向 offset 最少的那个看齐,来保证不特定多数服务器挂掉之后出现的数据无法同步。
Follower故障
会被 leader 临时踢出ISR。等到恢复后,follower 会读取 HW,并将 log 文件中大于 HW 的截掉,然后开始和 leader 的同步。
Leader故障
Leader 发生故障后,会从 ISR 中选出一个新的 leader。之后,为了保证多个副本之间的数据一致性,其余的 follower 会先将各自的log文件高于 HW 的部分截掉(新 leader 自己不会截掉),然后从新的 leader 同步数据。
评论区