参考文章
理论
拜占庭将军问题
- 分布式中多个节点,中间存在恶意篡改现象
- 拜占庭将军问题 (The Byzantine Generals Problem)
CAP
- C:一致性,保证读取到最新的数据
- A:可用性,服务可用,但不保证数据正确
- P:分区容错性,不管分布式系统内部出现什么样的数据同步问题,会一直运行
- 分布式系统中,P必须被考虑
- CA:单机系统
- CP:适合ACID(数据一致性)场景
- AP:适合BASE(服务可用性)场景
ACID
- 强一致性
- 使用场景:数据库
BASE
- 高可用性
- 基本可用 + 最终一致性
- 使用场景:NoSQL
- 基本可用:流量削峰、延迟响应、体验降级、过载保护
- 最终一致:读时修复,写时修复,异步修复
HA
- 高可用(High Availability)
强一致性
- 写操作完成后,任何后续访问都能访问到最新的数据
最终一致性
- 如果对某个对象没有后续写操作,最终所有后续访问都能读到相同的最近更新的值
分布式事务
- 场景:需要进行多个事务;比如中转机票需要同时定多张,那需要保证多张机票都可以被一起定到
反熵(Anti-entropy)
- 对比所有数据修复最终一致性
- Gossip使用
Hinted-handoff
- 写失败的请求,缓存到本地,周期性重试
- 缺点:失败请求超过缓存大小时,存在数据丢失问题
协议
分布式事务协议
- 场景:需要进行多个事务;比如中转机票需要同时定多张,那需要保证多张机票都可以被一起定到
- 做法:不管是数据层面还是业务层面,核心都是尝试更新/锁定资源、可以则提交,不可以则回滚
2PC
- 数据库层面
- 准备阶段(Prepare phase):锁定资源
- 提交阶段(Commit phase):Commit/Rollback,释放资源
- 锁定资源-执行操作/回滚
3PC
- 引入超时机制;加了一个准备状态
- CanCommit:询问是否可以提交
- PreCommit:预提交
- doCommit:正式提交
XA规范
- 数据层面的分布式事务协议
- 为数据库提供的2PC接口协议,基于数据库的XA协议来实现2PC又称为XA方案
TCC
-
业务层面 的分布式锁事务协议
-
Try-Confirm-Cancel
-
对于不同的业务场景,TCC三阶段采用不同的做法
-
场景:多个事务都涉及到更新数据库
- 做法:TCC在业务层面实现,不会锁定数据库,Try阶段可以将值先更新到一个额外字段,Confirm阶段再将额外字段的值更新到需要更新的字段
Paxos
Basic Paxos(共识算法)
-
多个节点就某个值(提案value)达成共识(达成最开始共识的一个值,达成共识后不再改变)
-
两阶段
-
准备阶段(Prepare):提议者们发送[n, _],接受者们表达准备响应
-
共识规则1 - Perpare阶段:小于等于当前接受者已经响应的提案编号,将被抛弃
-
提议阶段(Accept):提议者们发送[n, v],接受者们设置对应值
-
共识规则2 - Accept阶段:小于当前接受者已经响应的提案编号,将不通过提案
-
共识规则3:如果接受者已经通过过提案,将在响应中返回已通过的最大编号的提案具体信息
-
-
三种角色:
- 提议者(Proposer)
- 接受者(Acceptor)
- 学习者(Learner)
Multi Paxos(一致性算法)
-
就多个值达成共识
-
实现思路:领导者(Leader)/唯一提议者 + 只需要提议Accept阶段(因为只有一个提议者)
-
问题
- 读写都在主节点,存在性能问题
- 只考虑了怎么就一系列值达成共识,没有考虑各值的顺序性(demo在极客时间第15讲)
ZAB(Zookeeper Atomic Broadcast)
-
最终一致性
-
Multi Paxos缺点:能达成一系列值的共识,但是不保证顺序性
-
ZAB:
- 晚于Paxos,早于Raft
- 强领导者模型
- 两阶段提交
- 使用<任期,递增序号>来进行消息确认,保证顺序性
-
选主(ELECTION)
-
场景:发生故障后进行新的领导者选举
-
三种角色:Leader、Follower、Observer(没有投票权)
-
四种状态:Leading、Following、Observering、Looking(认为没有领导者,发起选主)
-
-
投票格式:<先前预设的自我标识sid、任期Epoch、上一次处理的事务Zxid,投票来自哪个节点node>
- 比较顺序:任期编号、事务ID(选择完整性最大的)、自我标识
-
和Raft区别:Raft有一个随机时间,先到先得;ZAB是根据数据进行PK
-
-
成员发现(DISCOVERY)
- 场景:选举完成后确认新的领导者和跟随者建立关系
- 接收到大多数节点的ACK后进入数据同步阶段
-
数据同步(SYNCHRONIZATION)
-
场景:领导者确定之后进行数据同步
-
peerLastZxid:跟随者节点上的事务标识符最大值
-
min/maxCommittedLog:领导者节点内存队列中,已提交的事务标识符最大值和最小值
- TRUNC:peerLastZxid > maxCommittedLog;跟随者丢失最新提案
- DIFF:minCommittedLog < peerLastZxid < maxCommittedLog;跟随者同步缺失的提案
- SNAP:peerLastZxid < minCommittedLog;跟随者接受领导者快照,进行同步
-
-
广播(BROADCAST)
- 写:Leader处理
- 读:所有节点都可以处理(所以是最终一致性)
-
ZAB和Raft区别
- 选举:ZAB进行数据PK;Raft用了随机超时时间和先到先得
- 成员变更:ZAB和Raft均支持
- 日志复制:以领导者日志为准,保证日志一致;并且必须按顺序提交,保证连续
- 读:ZAB是最终一致性;Raft是强一致性
- 写:都在主节点写
- 其他:Raft更简洁,更独立易用
Raft
- 强一致性 (强leader定序;读请求要依靠leader定序 + replication state machine(RSM))
三种角色
- 领导者(Leader)
- 跟随者(Follower)
- 候选人(Candidate)
This content is only supported in a Feishu Docs
三个指标
-
任期编号/任期
- 推选自己为候选人时候,任期+1
-
日志索引编号
-
随机超时时间
- 跟随者等待领导者心跳信息的时间,是随机的
- 候选人等待获得过半选票的时间,是随机的
三个阶段
-
领导者选举:
-
发生时机:
- 开始无领导者时候
- 指定时间内没有收到心跳信息,推选自己为候选人开始投票
-
投票规则:
- 一个成员一个任期内只能投一张票(先来先服务)
- 成员任期 < 其他节点任期:直接恢复成跟随者状态
- 成员任期 > 请求节点任期:拒绝请求
- 日志完整性高的节点,拒绝给日志完整性低的节点投票
-
-
获胜规则:赢得大多数选票的候选者
-
-
日志复制:
-
Leader收到大多数复制成功响应后,应用到状态机,返回成功给客户端
-
日志:索引值 + 任期 + 指令
-
日志必须是连续的
- Follower日志不同于Leader时候,Leader找到Follower上与自己相同日志项的最大所引致,强制覆盖
-
被复制到大多数节点上的日志,保证不被删除,不被覆盖
-
-
成员变更:
- 新加入/删除节点,会导致脑裂问题:一般采用单节点变更的方法
-
Raft特点:
- 一致性算法而不是共识算法
- 唯一领导者(任期 + 随机超时时间 + 先来先服务投票 + 心跳信息保证)
- 只有日志完整度较高的节点才能当选领导者(日志完整度投票规则保证)
开源实现
- GitHub - hashicorp/raft: Golang implementation of the Raft consensus protocol
- GitHub - baidu/braft(C++)
- GitHub - sofastack/sofa-jraft(Java)
- GitHub - goraft/raft
- GitHub - willemt/raft
- GitHub - logcabin
一致性哈希算法
-
Raft只能一个领导者,性能有瓶颈,多主节点如何分发
-
哈希算法
- 节点上线线时迁移数据量大
-
一致性哈希算法
-
-
节点上下线时迁移数据量小
-
问题:冷热不均
- 解决:引入更多的虚拟节点进行解决
-
Gossip
-
最终一致性
-
直接邮寄(Direct Mail):
- 发送更新数据
- 优点:只发送新数据,消耗小
- 问题:缓存队列满之后会丢失数据,无法保证最终一致性
-
反熵(Anti-entropy):
- 推、拉、推拉
- 优点:保证最终一致性
- 问题:对比所有数据成本过高
- 场景:存储组件中,所有节点都是已知的,用反熵修复最终一致性
-
谣言传播(Rumor mongering):
- 一个节点有了新数据,变为活跃状态, 会向其他节点发送新数据,直到所有节点都存储了新数据
- 场景:适合动态变化/节点数量较多的分布式系统
Quorum NWR
-
AP系统中临时要求强一致性
-
核心思想:N(副本数),W(写完W个节点才算写完),R(读完R个节点才算读到)
- W + R > N:强一致性(保证了必须读到一个强制写完的节点)
- W + R <= N:最终一致性
拜占庭容错算法
PBFT
-
达成一系列值的共识
-
通过签名(或消息认证码 MAC)约束恶意节点的行为,节点无法伪造另一个节点的消息
-
三个阶段(类似于三次握手?):
- f恶意节点个数计算公式:(n - 1) / 3
- Request(客户端请求)
- Pre-Prepare(领导者广播)
- Prepare(广播自己的指令)(Leader节点不广播)
- Commit(2f确认,广播提交消息)(Leader节点也广播)
- Reply(2f+1确认后执行Reply给客户端)
-
两个问题:
-
为什么是2f和2f + 1?
- Commit阶段Leader节点也加入进来了
-
为什么需要2f/2f + 1,而不是f + 1?
- 前提:某个阶段没有达成共识就返回给了客户端 ;2f或者f+1是包含自己的
- 问题:用f + 1可能导致前面的阶段都通过,最后Reply阶段,恶意节点不返回,导致客户端反复重试
-
-
局限性:
- 只能容忍(n - 1) / 3个恶意节点
- 时间复杂度为O(n^2)(O(n) + O(n * (n - 1)) + O(n * (n - 1))),只适用于中小型分布式系统
-
主节点叛变怎么办?
- 客户端在发现主节点出问题之后,会给所有节点发送消息,备份节点发现问题后,备份节点进行新的选主/视图变更
POW(Proof of Work)
- 客户端需要算出一个满足条件的值,证明自己做过一定量的工作;验证方可以很轻松的验证结果正确
- 具体实现:哈希函数不断算出指定的值,概率学问题