分布式技术专题「分布式技术架构」一文带你厘清分布式事务协议及分布式一致性协议的算法原理和核心流程机制(Paxos篇)

Posted 洛神灬殇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式技术专题「分布式技术架构」一文带你厘清分布式事务协议及分布式一致性协议的算法原理和核心流程机制(Paxos篇)相关的知识,希望对你有一定的参考价值。

概念简介

Paxos是一种基于消息传递具有高度容错特性的一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一。

发展历史

Paxos算法的发展历史追溯到古希腊,当时有一个名为“Paxos“的小岛, 岛上采用一会的形式通过法令, 议会中议员通过信使进行消息传递,议员与信使都是兼职的,他们随时都有可能会离开议会,并且信使有可能传递重复的信息,也有可能一去不复返,因此议会要保证在这种情况下法令仍然能够正确的产生,并且不会起冲突。

Paxos算法分析

对于Paxos算法而言要解决上述信息传递的一致性问题,那么要保证一下几点:

  • 在这些提案中,只有一个被选定
  • 如果没有提案被提出,就不会有选定的提案
  • 当提案被选定以后,进程应该可以获取被选定的提案信息

对于一致性来说,安全性需求如下

  • 只有被提出的提案才能被选定
  • 只有一个提案被选定
  • 如果某个进程认为某个提案被选定了,那么这个提案必须是真的被选定的那个

三种参与角色

  • Proposer(提议者)
  • Acceptor(决策者)
  • Leamner(最终决策学习者)
问题场景分析

一个元素参与者可能扮演多个角色 (Proposer | Acceptor | Leamner) ,假设不同的参与者之间可以通过收发消息来进行通信。每个参与者以任意的速度执行,可能会因为出错而停止,也可能会重启,消息在传输过程中可能会出现不可预知的延迟,也有可能会重复或者丢失,但消息不会被损坏,即消息内容不能被篡改。

Paxos算法场景问题分析

首先,我们采用将建立角色处理模式的场景化分析,先从Acceptor的模式开始处理和分析,分析对应的执行流程以及对应的问题。

单个Acceptor模式

在处于单Acceptor模式下的时候,如以下图所示。

最简单的选定方式是只有一个Acceptor, Proposer发送给该Acceptor提案以后, Acceptor直接选择第一个提案为被选定的提案。但这种做法一旦Acceptor出问题, 整个系统将无法正常工作。

多个Acceptor模式

Proposer向多个Acceptor集合发送提案, 每个Acceptor都可能会批准(Accept) 该提案, 当足够多个Acceptor批准这个提案的时候, 我们就认为该提案被选定了。

实现一致性的条件约束(1)

在没有失败和消息丢失的情况下,如果我们希望即使只有一个提案被提出,仍然可以选出一个提案,1个Acceptor必须批准他收到的第一个提案

该条件约束所出现的问题

如果多个提案被不同的Proposer同时提出, 这可能会导致虽然每个Acceptor都批准了他收到的第一个提案, 但是没有一个提案是多个人批准的,也就是没有多数的Acceptor集合,如下图所示。

为了解决此问题所以引入了【实现一致性的条件约束(2)】进行数据控制。

实现一致性的条件约束(2)

一个提案被选定需要被半数以上的Acceptor接受

它是在【实现一致性的条件约束(1)】的基础上, 一个Acceptor能够批准不止一个提案。我们使用全局的编号来唯一的标识每一个Acceptor批准的提案, 当一个具有某Value的提案被半数以上的Acceptor批准以后, 我们就认为该Value被选定。

注意:提案和value不是一个概念, 提案是由一个编号与value组成的结构体, 因此我们用【编号,Value】来表示一个提案

提案的结构体分析

提案的信息数据结构体主要有:提案编号+value两部分组成。

  • 提案编号:给每个提案加上一个提案编号,表示提案被提出的顺序,不同的编号可以有相同的内容。
  • value:提案的内容
该条件约束所出现的问题

虽然允许多个提案被选定, 但必须保证所有被选定的提案都具有相同的value值,否则又会出现不一致。

为了解决此问题所以引入了【实现一致性的条件约束(3)】进行数据控制。

实现一致性的条件约束(3)

如果提案编号为M, Value为V的提案(即【M,V】)被选定了,那么所有比M_编号更高的, 且被选定的提案, 其Value值必须也是V

因为提案编号是全序的, 【实现一致性的条件约束(3)】就保证了只有一个Value值被选定这一关键安全性属性。同时,一个提案被选定,其首先必须被至少一个Acceptor批准, 因此我们可以通过满足如下条件来满足【实现一致性的条件约束(3)】。

案例推荐

假设总的有5个Acceptor,Proposer2提出 [M1,V1] 的提案,Acceptor2~5(半数以上)均接受了该提案,于是对于Acceptor 2~5和Proposer2来讲, 它们都认为V1被选定。

Acceptor1刚刚从宕机状态恢复过来(之前Acceptor1没有收到过任何提案) , 此时Proposer1向Acceptor1发送了[M2, V2] 的提案(V2且M2>M1) ,对于Acceptorl来讲, 这是它收到的第一个提案。根据【实现一致性的条件约束(1)】(一个Acceptor必须接受它收到的第一个提案) ,从而Acceptor1必须接受该提案!同时Acceptor1认为V2被选定,这就出现了两个问题。

问题分析
  1. Acceptor1认为V2被选定,Acceptor2~5和Proposer2认为V1被选定,出现了不一致

  2. V1被选定了,但是编号更高的被Acceptor1接受的提案[M2,V2] 的value为V2,且V2不等于V1。且V2的编号还高于V1

实现一致性的条件约束(4)

如果一个提案【M,V】被选定后, 那么之后任何Proposer产生的编号更高的提案, 其Value的值都为V。

问题分析

如何确保在某个Value为V的提案被选定后, Proposer 提出的编号更高的提案的Value都是V呢?

实现分析

任意的N和V, 如果提案 [ N,V ] 被提出,那么存在一个半数以上的Acceptor组成的集合S,需要执行以下两个操作步骤:

  • 集合S内的每个Acceptor都没有批准过编号小于N的提案
  • 如果Acceptor已经接受过提案,那么就向Proposer响应已经接受过的编号小于N的最大编号的提案

Proposer生成提案

对于一个Proposer来说, 获取那些已经通过的提案远比预测未来可能会通过的提案来的简单。因此Proposer在产生一个编号为M的提案时, 必须要知道当前某一个将要或已经被半数以上Acceptor批准的编号小于M但未最大的编号的提案。并且,Proposer会要求所有Acceptor都不要批准任何编号小于M的提案。

Proposer生成提案之前(Prepare阶段)

应该先去学习已经被选定或者可能被选定的value,然后以该value作为自己提出的提案的value。如果没有value被选定, Proposer才可以自己决定value的值。这样才能达成一致。这个学习的阶段是通过一个 【Prepare阶段】 请求实现的。

  • 向Proposer承诺保证不再接受任何编号小于N的提案
  • 如果Acceptor已经接受过提案,那么就向Proposer响应已经接受过的编号小于N的最大编号的提案

提案生成算法

如果Proposer收到了平数以上的Acceptor的响应, 那么它就可以生成编号为N, Value为V的提案[N,V] 。这里的V是所有的响应中编号最大的提案的Value。如果所有的响应中都没有提案, 那么此时V就可以由Proposer自己选择。

Proposer生成提案之后(Accept请求)

Proposer将该提案发送给半数以上的Acceptor集合, 并期望这些Acceptor能接受该提案。我们称该请求为Accept请求。

注意:此时接受Accept请求的Acceptor集合不一定是之前响应Prepare请求的Acceptor集合

Acceptor批准提案

  • Acceptor可以忽略任何请求(包括Prepare请求和Accept请求) 而不用担心破坏算法的安全性。因此, 我们这里要讨论的是什么时候Acceptor可以响应一个请求。

  • 一个Acceptor只要尚未响应过任何编号大于N的Prepare请求, 那么他就可以接受这个编号为N的提案。

算法总结

阶段一

  1. Proposer选择一个提案编号M, 然后向Acceptor的某个超过半数的子集成员发送编号为M的Prepare请求。
  2. 如果一个Acceptor收到一个编号为M的Prepare请求, 且编号M大于该Acceptor已经响应的所有Prepare请求的编号, 那么它就会把已经批准过的最大的编号的提案作为相应反馈给Proposer, 同时该Acceptor会承诺不会在批准任何编号小于M的提案。

阶段二

  1. 如果Proposer收到来自半数以上的Acceptor对于其发出的编号为M的Prepare请求的响应,那么它就会发送一个针对【M,V】提案的Accept请求给Acceptor。

注意:V的值就是收到的响应中编号最大的提案的值,如果响应中不包含任何提案,那么他就是任意值

  1. 如果Acceptor收到的这个针对【M, V】的提案的Accept请求, 只要该Acceptor尚未对编号大于M的Prepare请求作出响应, 他就可以通过这个提案。

看到这里是不是觉得和我们分布式事务中的2PC的思路和流程差不多啊!

通知学习Learner的方案

方案1

一旦Acceptor批准了一个提案, 就将该提案发送给所有的Leamer

方案2

让所有的Acceptor将它们对提案的批准情况, 统一发送给一个Learner, 再由它通知其他的Learner

方案3

方案2的主节点存在单点问题, 可以将主Leaner的范围扩大, 即Acceptor可以将批准信息发送给一个特定的Learner集合, 该集合中每个Leamer都可以在一个提案被选定后通知其他Leaner。

给你们的问题

  1. 设置多少个Acceptor最为合适?

  2. 如何控制每个Acceptor最多只能批准一个提案?

分布式技术专题「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析

分布式架构

互联⽹架构演进

单体应⽤架构
定义

⼀个归档包(例如war格式)包含所有功能的应⽤程序,我们通常称为单体应⽤。⽽架构单体应⽤的⽅ 法论,就是单体应⽤架构。

架构示意图

【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析_运维

优缺点分析
  • 初期:
  • 架构简单,统一化管理真个服务和生态体系
  • 开发、测试、部署都很⽅便
  • 随着项⽬的发展,项⽬越来越臃肿,问题出现了:
  • 复杂性⾼
  • 只有逻辑性的模块化,物理不隔离,随着代码的增⻓也越来越混乱、复杂。⼀个百万⾏级别的单体应⽤为例,整个项⽬包含的模块⾮常多,模块的边界模糊,依赖关系不清晰,代码质量参差不⻬,混乱地堆砌在⼀起……整个项⽬⾮常复杂。每次修改代码都⼼惊胆战,甚⾄添加⼀个简单的功能,或者修改⼀个BUG都会带来隐含的缺陷。
  • 技术债务
  • 时间推移、需求变更和⼈员更迭,会逐渐形成应⽤程序的技术债务,并且越积越多。“不坏不修(Not broken,don’t fix)”,这在软件开发中⾮常常⻅,在单体应⽤中这种思想更甚。已使⽤的系统设计或代码难以被修改,因为应⽤程序中的其他模块可能会以意料之外的⽅式使⽤它。
  • 部署频率低
  • 随着代码的增多,构建和部署的时间也会增加。⽽在单体应⽤中,每次功能的变更或缺陷的修复都会导致我们要重新部署整个应⽤。全量部署的⽅式耗时⻓、影响范围⼤、⻛险⾼,这使得单体应⽤项⽬上线部署的频率较低。⽽部署频率低⼜导致两次发布之间会有⼤量的功能变更和缺陷修复,出错概率⽐较⾼。
可靠性较差,影响范围大

极端场景下,某个BUG,例如死循环、OOM等,可能会导致整个应⽤的崩溃。

扩展能⼒受限

单体应⽤只能作为⼀个整体进⾏扩展,⽆法根据业务模块的需要进⾏伸缩。

  • 模块是计算密集型的,它需要强劲的CPU;
  • 模块则是IO密集型的,需要更⼤的内存。

由于这些模块部署在⼀起,我们不得不在硬件的选择上做出妥协。

阻碍技术创新

单体应⽤往往使⽤统⼀的技术平台或⽅案解决所有的问题,团队中的每个成员都必须使⽤相同的开发语⾔和框架,要想引⼊新框架或新技术平台会⾮常困难。例如,⼀个使⽤Struts 2构建的、有100万⾏代码的单体应⽤,如果想要换⽤Spring MVC,毫⽆疑问切换的成本是⾮常⾼的。

微服务架构

诞⽣背景
  1. 互联⽹的迅速发展 => 敏捷
  2. 快速验证、快速试错 => 持续交付
  3. 更频繁的部署频率 => 要⾃动化,DevOps
  4. 单体架构⽆法满⾜要求
微服务是什么

微服务架构⻛格是⼀种将⼀个单⼀应⽤程序开发为⼀组⼩型服务的⽅法,每个服务运⾏在⾃⼰的进程中,服务间通信采⽤轻量级通信机制(通常⽤HTTP资源API)。这些服务围绕业务能⼒构建并且可通过全⾃动部署机制独⽴部署。这些服务共⽤⼀个最⼩型的集中式的管理,服务可⽤不同的语⾔开发,使⽤不同的数据存储技术。

TPS分布式架构

Martin Fowler《微服务》博客原⽂:http://www.martinfowler.com/articles/microservices.html,译​ ⽂:​http://blog.cuicc.com/blog/2015/07/22/microservices/​

微服务优缺点
优点:
  • 单个服务更易于开发、维护
  • 单个微服务启动较快
  • 局部修改容易部署
  • 技术栈不受限
  • 按需伸缩
缺点:
  • 运维要求⾼
  • 分布式固有的复杂性
  • ⽹络延迟、容错、分布式事务...
  • 重复劳动
微服务 vs SOA

【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析_设计原则_02

微服务设计原则
  • 单⼀职责原则 SOLID
  • 服务⾃治原则
  • 轻量级通信机制
  • 合理的微服务粒度
  • ⾃动化机制:⾃动化测试、部署、运维,⼀切。
调⽤关系设计原则
  • 尽量缩短链路
  • 避免循环依赖
微服务架构通览

【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析_设计原则_03

服务发现原理+负载均衡原理剖析
最初的模式

【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析_微服务_04

单体应用集中式部署

【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析_设计原则_05

多个应用集中式部署(集群式)

【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析_设计原则_06

多应用分布式部署(分布式)

【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析_设计原则_07

多应用分布式部署+TDDL分布式代理数据

【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析_微服务_08

微服务拆分⽅方案
领域驱动设计(DDD)
  • 领域模型:为了准确定义需要解决的问题⽽构造的抽象模型。
  • 领域语⾔ - 定义领域模型时,⼀定要基于某个特定场景,建⽴⼀个⽆歧义的交流语⾔。例如UML
  • 界限上下⽂ - 限定某个特定边界的上下⽂。对于相同的模型⽽⾔,在不同的场景强调的场景是不太⼀样的。例如:对于订单⽽⾔,从⽤户的视⻆分析,⽤户关⼼的是订单的价格、快递到的时间;从库存的⻆度分析,库存更关注订单的库存的告警等等。
⾯向对象
  • by name:对象模型
  • by verb:对象⾏为。
微服务拆分最佳实践

初期使⽤⾯向对象/DDD建模后拆分即可,不要过分纠结微服务粒度,也不宜拆分得过细(防⽌出现很多不合理的拆分粒度),满⾜当前要求即可。当随着业务的发展,业务场景需要我们进⼀步细化拆分的粒度再去细化拆分或改造。

项⽬改造成微服务

改造前须知
  1. 改造不是⼀蹴⽽就的,往往很⻓⼀段时间内,新⽼架构是共存的
改造原则
  1. 初期只做必要的微服务治理
  • 缩⼩改造范围,⼩范围试⽔
  1. 降低对已有系统的侵⼊性
  • 尽量减少对现有系统的改动,降低⻛险,保证可⾏性
  • 初期挑选相对独⽴、耦合性较⼩的上层应⽤进⾏改造,积累经验
  • 经验丰富后,再进⾏⼤规模的改造
新⽼架构共存
  1. 只能⼀点⼀点改造,并逐步将遗留项⽬迁移⾄微服务架构。
老应用依赖新应用

【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析_运维_09

调用模式

【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析_设计原则_10

新应用依赖老应用

【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析_运维_11

⽼服务暴露基于HTTP的API,新⽼服务之间通过Spring Cloud Sidecar进⾏通信,Sidecar原理解析。

代码改造方式
绞杀者模式

【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析_设计原则_12

  • 对于遗留系统Legacy
  • 不改动⽼系统Legacy,⽽是另起微服务Modern,实现对Legacy的改造
  • 外层创建⼀个⻔⾯,做接⼝的转发,对于已经改造完的API,转发到Modern,对于未改造的API,
  • 依然转发到Legacy
  • 使⽤⽹关配置转发规则
  • 随着迁移的不断深⼊,最终绞杀掉Legacy
修缮者模式

【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析_运维_13

  • 识别出应⽤要拆的部分(Flawed Supplier)
  • 为(Flawed Supplier)抽象接⼝层(Abstraction Layer),从⽽隔离(Flawed Supplier)内部发
  • ⽣的变化
  • “客户端”通过接⼝层(Abstraction Layer)调⽤(Flawed Supplier)
  • 为要拆的部分(Flawed Supplier)编写新的实现(New Supplier)
  • “客户端”通过接⼝层调⽤新的实现(New Supplier)
  • 删除Flawed Supplier
数据库层⾯的改造
  • 废弃掉掉触发器、存储过程
  • ⼲掉跨模块的join,使⽤API调⽤的⽅式实现原有功能
  • 将API按照模块拆出去
  • 将相关的表拆到对应的微服务种
  • 业务改造期间,通过双写⽅式
  • 切少量流量到新库,进⾏测试与验证
  • 流量全切到新库
  • 逐步退化⽼库,直⾄废弃
  • 迁移失败预案

以上是关于分布式技术专题「分布式技术架构」一文带你厘清分布式事务协议及分布式一致性协议的算法原理和核心流程机制(Paxos篇)的主要内容,如果未能解决你的问题,请参考以下文章

分布式技术专题带你彻底认识Paxos算法Zab协议和Raft协议的原理和本质

一文带你了解大数据技术之Zookeeper(入门级)

一文带你解读 Redis分布式锁发展史和正确实现方式

分布式技术专题「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析

深入浅出Seata原理及实战「入门基础专题」带你透析认识Seata分布式事务服务的原理和流程

分布式技术专题「架构实践于案例分析」总结和盘点目前常用分布式技术特别及问题分析