分布式协议与算法实战-理论 CAP BASE

在分布式系统中,最重要的事就是如何选择或设计最合适的算法,解决一致性和可用性相关问题。

Raft 适合性能要求不高的强一致性场景。

重试、幂等、异步、负载均衡、故障隔离、流量切换、自动扩缩容、熔断、限流、降级、容量规划

拜占庭将军问题

讨论了分布式共识问题,分布式领域最复杂的容错模型。

假设 ABC 三个将军同时率军攻打一个目标,他们分布在不同区域,只能通过信使联系,且有半数军力一起进攻才能取胜。这时有一个问题:如何统一作战计划?此时可能有叛徒案通敌军发送假消息;可能信使被劫杀;可能混入间谍。这里采取少数服从多数,如3人中有2人一致即可。

问题:存在叛徒

A向BC发送“撤退”,B向AC发送“进攻”,C向A发送“撤退”向B发送“进攻”。根据少数服从多数,A选择撤退,B选择进攻。但进攻的只有B,使得行动不一致。这就是“二忠一叛难题”。

解决办法1:口信消息型拜占庭问题解

增加一个将军S,这样3人讨论变为4人讨论,能增加忠诚将军的数量。4人还约定没收到命令是执行预设的默认命令,如“撤退”。此外还进行两轮信息协商:

第一轮:

  • 先发送消息的作为指挥官,其他为副官
  • 指挥官将他的作战信息发送给每位副官
  • 每位副官将收到的信息作为作战指令;若没有则执行默认命令,如“撤退”

第二轮:

  • 除第一轮指挥官,剩余3位将军分别作为指挥官向另外2位发送作战信息
  • 这3位按照少数服从多数执行指令

假设忠诚将军先发送消息

第二轮中叛徒C发送不同信息

AB收到的作战信息都是“进攻、进攻、撤退”,于是SAB一起执行进攻,保证了作战的一致性

假设叛徒先发送消息

第二轮中

SAB收到的都是“撤退、撤退、进攻”,所以三者一起撤退,保证了作战一致性。

也是说叛徒如何捣乱都不会影响一致性。

小结

如果叛徒人数为m,将军总数不能少于3m+1,那么拜占庭将军问题就被解决。

这个算法的前提就是能容的叛徒数为m,是已知的。这个算法中叛将数m决定递归循环次数,即m+1轮。也就是n位将军最多容忍(n-1)/3位叛徒。具体过程见论文。

解决办法2:签名型拜占庭问题之解

通过签名等方式可以在不增加人数的情况下解决“二忠一叛”问题。签名需要:无法伪造、不可篡改、能验真伪。

当忠诚将军先发信息时,其他将军能发现叛徒发送的信息是伪造的

当忠诚将军A先发送消息,C接收到B的假消息后进行验证,发现消息被修改、B已叛变,于是执行A的消息。

当叛徒B先发送消息,A和C将发现B给他们发送的消息不一致,即B已经叛变。

通过签名机制,无论有多少忠诚的将军和叛徒,忠诚的将军总能达成一致。

总结

  • 忠诚的将军:正常的计算机
  • 叛变的将军:出故障并会发送误导信息的计算机
  • 信使被杀:通讯故障、信息丢失
  • 信使被间谍替换:中间人攻击、恶意伪造信息和劫持通讯

拜占庭将军问题是最困难的,也是最复杂的一种分布式故障情况,除了故障还存在恶意行为。

  • 当存在恶意行为时必须使用拜占庭容错算法(Byzantine Fault Tolerance,BFT)如区块链技术中,常见拜占庭容错算法还有:PBFT、PoW
  • 更常见的是非拜占庭容错算法,即故障容错算法(Crash Fault Tolerance,CFT)如内部机器集群。CFT解决的是分布式系统中存在故障,但不存在恶意节点场景下的共识问题。即有消息丢失重复,但没有错误消息。如:Paxos、Raft、ZAB

CAP理论

开发分布式系统时要根据分布式系统的业务特点设计合适的分区容错一致性模型

  • 一致性(Consistency)
    无论访问哪个节点,要么读到同一份最新数据,要么读取失败。
    注意:一致性不是数据完整性,而是各节点间的数据一致性。
  • 可用性(Availability)
    无论访问哪一个节点都能得到相应数据,但是不保证是同一份最新数据。强调服务可用而非数据一致
  • 分区容错性(Partition Tolerance)
    当出现任意数量的消息丢失或高延迟,系统仍可提供服务。强调集群对分区故障的容错能力。在分布式系统中这是必须考虑的

CAP不可能三角

基于证明严谨性的考虑,理论证明中对指标的含义做了预设和限制,如将一致性限制位原子性。

如何选择?

只要有网络就一定有延迟和数据丢失,也就是分区容错性(P)一定要保证。

  • 当选择一致性(CP)如果部分节点无法保证特定信息最新,这时集群接收到客户端的写请求时因无法保证所有节点都是最新信息,所以拒绝服务即不可用
  • 当选择可用性(AP)系统始终处理客户端查询,返回特定信息,若发生网络分区,则返回当前分区相对新的信息

当不存在网络分区时,即分布式系统正常运行时可以同时保证AC,只有发生分区故障时才需要P。当各节点数据不一致,影响到系统运行或业务时选C,否则选A。

例子:为InfluxDB添加分布式供能

InfluxDB由META和DATA两个逻辑单元组成,这两个节点的功能和数据特性不同,需要分别设置分区容错一致性模型。

  • 作为分布式系统P必须要实现
  • META 节点是系统运行的关键,必须所有节点一致防止记录不一致影响系统运行。因此选择CP
  • DATA节点保持具体数据记录。虽不是系统运行相关元信息,但服务被频繁访问,水平拓展、性能、可用性等是关键。选AP

如果做了错误选择,如DATA节点选择了流行的Raft算法,会有什么问题?

  • Raft是CP模型,所有请求在领导者节点上处理,整个集群性能等于单机性能,使得性能低下无法支撑大数据
  • 受限于raft的强领导模型,及raft的节点和副本一一对应的限制,无法实现水平扩展,集群只能拓展读性能不能提升写性能
  • raft的“一切以领导者为准”的日志复制特性,会导致DATA节点丢数据

总结

  • CA,在分布式中不存在,如单机版MySQL,考虑主备或集群部署MySQL时要考虑P
  • CP,一旦网络分区就会影响用户的体验和业务的可用性,为了防止数据不一致,集群将拒绝服务。如:zookeeper、etcd、hbase
  • AP,实现高可用,无论何时都能得到影响,当出现错误时访问不同节点得到不同相应数据,如Cassandra、DynamoDB

另外延迟是个重要指标,如:通过延迟评估服务可用性进行负载均衡和容灾、在raft中通过延迟评估leader和可用性等

ACID

单机上的事务涉及到加锁、时间序列等,分布式上的事务涉及到二阶段提交协议TCC(Try-Confirm-Cancel)。

场景:client提交任务,需要ABC三个节点共同完成,要么全都完成,要么全不完成

二阶段提交协议

client将任务提交给A,然后A扮演协调者身份,联系BC发起二阶段提交

1. 提交请求阶段(投票阶段)

  1. A向BC提问:能否执行任务
  2. ABC分别评估自己能否执行,可以就预留时间并锁定,不再进行其他活动
    即评估事务中要操作对象和对象状态,是否准备好,能否提交
  3. A得到所有回复,包括自己的,都是Yes,然后进入下一阶段

2. 提交执行阶段(完成阶段)

  1. 按照“全部完成或放弃”的原则统计投票结果,所有回复都是同意则执行事务
  2. 通知BC执行事务
  3. BC执行事务并返回事务结果
  4. A拿到所有节点的事务结果并返回给client

注意:
第一阶段中每个参与者投票表决放弃还是提交。一旦要求提交事务就不允许放弃,即某个参与者投票要求提交事务前必须保证能够执行提交协议中自己的那一部分,即使参与者中途出现故障或中途被换掉。
第二个阶段中所有节点执行最终统一决定,提交事务或放弃,是为了实现ACID中原子性。

MySQL就是通过改进的XA协议实现了分布式事务。

但是二阶段提交协议存在一些问题,使得无法根据业务特点调整锁的粒度,进而影响数据库并发性:

  • 提交请求阶段要预留资源,期间其他人不能操作
  • 数据库是独立的系统,不会根据业务需求修改数据库逻辑

TCC

终于有人把“TCC分布式事务”实现原理讲明白了! - JaJian - 博客园 (cnblogs.com)

Try-Confirm-Cancel 的简称,包含预留、确认和撤销这2个阶段,此时客户端S即为协调者

1 预留阶段

  1. 由S分别发送消息通知ABC预留资源用于执行事务,然后S实现确认和撤销操作
  2. 当ABC都回复success时进入确认阶段,否则进入撤销阶段

2 确认阶段

  1. S执行确认操作,通知ABC执行事务
  2. ABC执行事务并向S确认,完成分布式任务

3 撤销阶段

  1. S执行撤销操作,通知ABC取消事务
  2. ABC取消并向S响应

之后ABC要么全都完成事务,要么都不做。

TCC 本质是补偿事务,针对每个操作注册一个与其对应的确认操作和补偿操作(即撤销操作)。这是一个业务层面的协议,要在业务代码中编程实现。为了保证一致性,确认操作和补偿操作必须等幂,因为其可能失败重试。TCC虽然能够减轻数据库的压力,但是对业务代码入侵程度也越高,可以有限考虑 MySQL XA 等

总结

  1. 二阶段提交协议,是一种很经典的思想。在达成提交共识的算法中广泛应用:XA协议、TCC、Paxos、Raft
  2. 幂等性,同一操作对同一系统的任意多次执行产生的影响和一次执行的相同。实现方法有Token、索引等。本质是通过唯一标识、标记同一操作的方式消除多次执行的副作用
  3. Paxos、Raft等强一致性算法也采用了二阶段提交操作,在提交请求阶段只要大多数节点确认即可具有ACID特性事务。可将有ACID特性的操作理解为最强一致性

针对二阶段提交协议的“协调者故障,参与者长期资源锁定”的痛点引入了询问阶段和超时机制,来减少资源长时间锁定的痛点,不过这也导致正常情况下用更多的消息协商,增加系统负载和延迟。

ACID为CAP中一致性的边界,必然影响可用性。考虑到大部分场景可以接受短暂的不一致性和可用性、并发性能,尽量不使用分布式事务,可考虑强一致性最终一致性

BASE 理论

追求可用性是AP的衍生。根据实际场景,尽量采用可用性优先的AP模型。AP模型是一个动态模型,基于业务场景妥协折中实现,没有现成的方案。

核心是基本可用(Basically Available)最终一致性(Eventually consistent)
还有软状态(soft state)即一种过渡状态

基本可用

当系统遇到不可预知故障时允许损失部分功能的可用性,保障核心功能的可用。如突发大流量时可使用服务降级保障部分可用。

  1. 流量削峰:12306将不同城市的票在不同时间点开售
  2. 延迟相应:购票请求要排队几分钟才能成功
  3. 体验降级:在面临突发网络请求时减小图片大小、降低清晰度
  4. 过载保护:请求放入队列,请求等待超时直接拒绝超时请求;队列满后清楚队列中一定数量请求使系统不过载

基本可用是一种妥协,在出现节点故障或系统过载时通过牺牲非核心功能保障核心功能稳定。

最终一致性

系统中所有数据副本经过一段时间同步后,最终能够达到一个一致状态。即在数据一致性上存在延迟。多数互联网系统采用最终一致性,只有对于决定系统运行的敏感元数据需要考虑强一致性,对于与钱有关的支付系统或金融系统数据要考虑事务。

  • 无法容忍一致性的延迟,如分布式锁对应的数据,要实现强一致性
  • 能容忍短暂一致性的延迟,如用户状态,可考虑最终一致性

强一致性可看作最终一致性的特例,即不存在延迟的一致性。数据有两种标准:

  1. 最新写入数据为准,如AP模型的KV存储
  2. 以第一次写入的数据为准,若不希望存储数据被修改则以其为准

实现最终一致性的具体方式:

  1. 读时修复:读数据时检测一致性并修复
    Cassandra 的 Read Repair,查数据时检测到不同节点副本数据不一致,系统自动修复
  2. 写时修复:写数据时检测一致性并修复
    Cassandra 的 Hinted Handoff 实现,节点间远程写数据时若写失败就缓存数据并定时重传,修复数据不一致性
  3. 异步修复:最常用,定时对账检测副本数据一致性并修复

写时修复无需数据一致性对比,消耗低影响不大,推荐使用。读时修复和异步修复因为要一致性对比数据,性能消耗较多。

如何使用

以拓展InfluxDB中DATA节点集群为例,可以通过分片和多副本实现读和写的基本可用。即将同一业务数据先分片再以多份副本的方式分布在不同节点上。下图中除非超过一半节点故障,否则所有数据都能读写。

总结

BASE是CAP中AP的选择。核心是:如果非必须,不推荐实现事务或强一致性,鼓励AP,根据业务实现基本可用及部分数据的最终一致性。对于任何集群不可预知故障的后果都是系统过载,如何设计过载保护,保证系统过载时基本可用时重中之重。

posted @ 2023-03-15 17:43  某某人8265  阅读(127)  评论(0编辑  收藏  举报