携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情 >>
1. 分布式系统
- 背景:随着数据规模越来越大,对服务的可用性要求越来越高,快速迭代的业务要求系统足够易用
- 维度:高性能;正确;可靠
- 一致性的定义:对于多台机器,要像操作一台机器一样,读到正确的最近写入的值。一致性是一种模型月约定一个分布式系统如何向外界应用提供服务
2. 实际设计和实现
如何设计kv既高可用又有一致性——从HDFS开始
- 接口Get,BatchPut
- 第一次实现RPC,DB Engine
单线程,使得所有的操作顺序进行
2.1 从复制开始
-
一台机器会存在宕机的可能,使用备份
- 主副本定期拷贝全量数据到从副本
- 主副本拷贝操作到从副本
-
how
- 主副本把所有的操作打包成log(log会持久化到磁盘上)
- 应用包装成状态即,只接受Log作为Input
- 主副本确认Log已经成功写入到副本机器上,当状态机apply后,返回客户端
-
当意外发生,比如主副本失效
- 可用性:人立即手工切换,能够保证较高的可用性
- 如何保证真的失效?可能会出现脑裂问题
- 容错性:增加到三个节点之后,会有长尾现象的发生,要允许少数节点失效,仍然可以工作
-
脑裂问题:一个分布式系统中的机器不一致,对于两个不同客户端,存在两个不同状态,提供不同的结果。
- 脑裂现象会导致两个节点都认为自己是这个集群的主节点,开始争抢彼此的共享资源和应用服务,这样就会发生严重后果
2.2 读操作
- 直接读状态即,要求写操作进入状态机后再返回client
- 写操作复制完成后直接返回,读操作Block等待所有的pending log进入状态机
2.3 一致性协议
KV中常见的一致性模型
- 最终一致性:读取可能暂时读不到但是总会读到
- 线性一致性:最严格,线性执行
2.4 共识算法
简而言之一个值一旦确认,所有人都认同
达成共识是个不可能的任务,因为
- 错误总是会发生
- 错误的类型有很多,比如网络断开,分区,缓慢,重传,乱序,cpu停滞
- 容错
共识算法不等于一致性,应用层面不同的一致性都可以用共识协议来实现。简单的复制协议也可以提供线性一致性。
弱一致性能够使用简单的复制算法实现,共识协议提到的一致性是线性一致性
3. Raft一致性算法
Paxos:基本上是一致性协议的同义词。由于Paxos算法难以理解,工程上实现需要做一些修改。
Raft:2014年发表,易于理解。使用了RSM、Log、RPC的概念;直接使用RPC对算法进行描述;Strong Leader-based;使用了随机的方法减少约束;正确性也可以保证。TiDB和ETCD都使用了Raft算法
3.1 复制状态机(RSM)
replicated state machine: raft中所有的共识都使用Log作为载体
Commit Index:一旦Raft更新Commited Index,意味着这个Index前的所有Log都可以提交给状态机了。不持久化,状态机重启后从第一条Log开始。
3.2 角色
Raft算法中除了领导者(Leader),还支持其它两种成员身份(服务器状态)分别是跟随者(Follower)、候选人(Candidate)。在任何时候,每一个服务器节点都处于这3个状态中的一个。
1)跟随者(Follower):相当于普通群众,默默地接收和处理来自领导者的消息,当领导者心跳超时的时候,它就会主动站出来,推荐自己当候选人。
2)候选人(Candidate):向其他节点发送请求投票(RequestVote)RPC消息,通知其他节点来投票,如果赢得了大多数选票,就晋升为领导者。
3)领导者(Leader):相当于决策者,平常的主要工作内容就是3部分,处理写请求、管理日志复制和不断地发送心跳信息,通知其它结点“我是领导者,还活着,不要发起新的选举”。
3.3 整体流程
- 假设在集群中,有A、B、C三个节点。初始状态下,所有的服务器都是跟随者(Follower)的状态,而没有领导者状态(Leader
- 集群中每个节点都有一个选举超时时间(election timeout),每个节点的选举超时时间都是150ms~300ms的一个随机数,每当节点的选举超时时间到了就会触发它成为候选者。假设A节点的等待超时时间最小(150ms),它会最先因为没有等到领导者的心跳信息,发生超时。此时A节点的状态会由Follower转换成为Candidate状态。
- A转换了自己的状态为Candidate,它会立即开始如下动作进行选举:1)增加了自己的term,2)先给自己投上一票,3)重置选举超时时间,4)然后向其他节点发送请求投票RPC消息,请它们选举自己为领导者。如果候选人在选举超时时间内赢得了大多数选票,它将成为本届任期内新的领导者。
3.4 Term任期
Raft算法把时间分为任意不同长度的任期(term),每一个任期的开始都是领导人选举。term是一个全局的、连续递增的整数,每进行一次选举,term就会加一。
- 如果处于Candidate状态状态的节点赢得了选举,它会当领导者直到任期结束。在成功选举之后,一个领导人会在任期内管理整个集群。
- 如果选举失败,该任期就会因为没有领导人而结束。
为了保证Raft的安全性,在一个term中最多只会有一个leader
3.5 主节点失效
A当选领导者后,它将周期性地发送心跳信息,通知其它服务器“我是领导者”,阻止跟随者选举超时,发起新的选举。
跟随者们有不同的出发选举超时的时间,如果长时间没有收到心跳信息,跟随者发起一轮新的选举来选出一个新的领导人。
3.6 节点间通信
在Raft中,服务器节点间的沟通采用是远程过程调用(RPC),在领导者选举中,需要用到两类RPC。
1)请求投票RPC(RequestVote)RPC:是由Candidate发送给其他节点,请求其他节点为自己投票,如果一个Candidate获得了多数节点的投票,则该Candidate转变为Leader。
2)日志复制(Append Entries)RPC:是由Leader节点发送给其他节点,有两个作用,当其entries域为空时,该RPC作为Leader的心跳,当entries域不为空时,请求其他节点将entries域中的log添加到自己的log中。日志复制RPC只能由领导者发起。
- 日志是对于数据一致的关键保证。副本数据是以日志的形式存在的,日志是由日志项组成。日志项是一种数据格式,它主要包含用户指定的数据,也就是指令(Command),还包含一些附加信息,比如索引值(Log index)、任期编号(Term)。索引值记录了在日志中的位置,任期编号表明该记录首次被创建时的任期号,可以用来检测在不同服务器上日志的不一致性。Raft日志的index和term唯一标示一条日志记录。
3.7 安全性验证
Raft使用TLA+进行了形式验证
4. Raft算法细节
4.1 集群成员变更
随着时间推移,会有机器故障需要替换,或者更改复制级别,修改节点数量。虽然通过关闭整个集群,升级配置文件,然后重启整个集群也可以解决这个问题,但是这回导致在更改配置的过程中,整个集群不可用,并且还存在操作失误的风险。
为了避免发生脑裂现象,使用一个两阶段(two-phase)协议。Raft通过共同一致(Joint Consensus)来完成两阶段协议,即:新、旧两种配置上都获得多数派选票。
4.2 水平扩展
多个副本只有单个副本可以提供服务是,服务无法进行水平扩展
4.3 日志压缩
当日志信息过多时,启用snapshot替换掉Log
快照一般包含以下内容:1)日志的元数据:最后一条被该快照apply的日志term及index;2)状态机:前边全部日志apply后最终得到的状态机。
4.4 选举合法leadership
- 通过一轮心跳确定Leadership,同时保证选举时间不会有新的跟随者timeout
5. 更多优化
实际上系统需要us级别的共识,以及us级别的容错
5.1多节点提交
涉及到节点跨地域,节点间的RTT(Round Trip Time)很大
- EPaxos使用了冲突图的方式来允许并行Commit
- 不冲突的情况下减少RTT的提交时间