《分布式原理与算法解析》学习笔记
四横:分布式计算、分布式数据存储与管理、分布式通信、分布式资源池化;四纵:分布式协同、分布式调度、分布式追踪与高可用、分布式部署。
在一定的资源上,进行一定通信,通过一定计算,完成一定数据的加工和处理,从而对外提供特定的服务。
分布式其实就是将相同或相关的程序运行在多台计算机上,从而实现特定目标的一种计算方式。
产生分布式的最主要驱动力量,是我们对于性能、可用性及可扩展性的不懈追求。
性能、资源、可用性和可扩展性。
QPS:查询数每秒,TPS:事务数每秒,BPS:比特数每秒
系统的可用性可以用系统停止服务的时间与总的时间之比衡量。
可扩展性,指的是分布式系统通过扩展集群机器规模提高系统性能(吞吐量、响应时间、完成时间)、存储容量、计算能力的特性,是分布式系统的特有性质。
在分布式系统中,排他性的资源访问方式,叫作分布式互斥。这种被互斥访问的共享资源叫作临界资源。
解决这个问题的方法有:
霸道总裁:集中式算法,也可以叫中央服务器算法。
民主协商:分布式算法。
轮值CEO:令牌环算法。
对于一个集群,多个节点如何协同,怎么管理。
选举一个“领导”来负责调度和管理其他节点。
分布式选举常见算法:
长者为大:Bully算法
在所有活着的节点中,选取ID最大的节点作为主节点。
民主投票:Raft算法
具有优先级的民主投票:ZAB算法
分布式共识就是在多个节点均可独自操作或记录的情况下,使得所有节点针对某个状态达成一致的过程。通过共识机制,可以使得分布式系统中的多个节点的数据达成一致。
常见分布式共识方法:
PoW(Proof-of-Work,工作量证明):是以每个节点或服务器的计算能力来竞争记账权的机制,是一种使用工作量证明机制的共识算法。
PoS(Proof-of-Stake,权益证明):由系统权益代替算力来决定区块记账权,拥有的权益越大越获得记账权的概率就越大。
DPoS(Delegated Proof of Stake,委托权益证明):类似股份制公司的董事会制度,普通股民虽然拥有股权,但进不了董事会,他们可以投票选举代表(受托人)代他们做决策。DPoS 是由被社区选举的可信帐户(受托人,比如得票数排行前 101 位)来拥有记账权。
一致性强调的是结果,共识强调的是达成一致的过程,共识算法是保障系统满足不同程度一致性的核心技术。
关于事务:ACID
A:Atomicity , 原子性,事务要么全部执行,要么全部不执行。
C: Consistency,一致性,事务执行前到执行后,数据的完整性要保持一致。
I: Isolation,隔离性,多个事务并行执行时,互不干扰。
D: Durability,持久性,一个事务完成了,对数据库的操作就永久保存了。
分布式事务,就是在分布式系统中运行的事务,由多个本地事务组合而成。
BASE 理论,该理论的一个关键点就是采用最终一致性代替强一致性。
常见的实现分布式事务的方法:
基于 XA 协议的二阶段提交协议方法:协调者下发请求事务操作,参与者将操作结果通知协调者,协调者根据所有参与者的反馈结果决定各参与者是要提交操作还是撤销操作。
三阶段提交协议方法:三阶段提交引入了超时机制和准备阶段。
基于消息的最终一致性方法:例如MQ消息中间件。
BASE 理论包括基本可用(Basically Available)、柔性状态(Soft State)和最终一致性(Eventual Consistency)。
基本可用:分布式系统出现故障的时候,允许损失一部分功能的可用性。比如,某些电商 618 大促的时候,会对一些非核心链路的功能进行降级处理。
柔性状态:在柔性事务中,允许系统存在中间状态,且这个中间状态不会影响系统整体可用性。比如,数据库读写分离,写库同步到读库(主库同步到从库)会有一个延时,其实就是一种柔性状态。
最终一致性:事务在操作过程中可能会由于同步延迟等问题导致不一致,但最终状态下,数据都是一致的。
锁是实现多线程同时访问同一共享资源,保证同一时刻只有一个线程可访问共享资源所做的一种标记。
与普通锁不同的是,分布式锁是指分布式环境下,系统部署在多个机器中,实现多进程分布式互斥的一种锁。为了保证多个进程能看到锁,锁被存在公共存储(比如 Redis、Memcache、数据库等三方存储中),以实现多个进程并发访问同一个临界资源,同一时刻只有一个进程可访问共享资源,确保数据的一致性。
实现分布式锁的 3 种主流方法,即:
基于数据库实现分布式锁,这里的数据库指的是关系型数据库;
基于缓存实现分布式锁;
基于 ZooKeeper 实现分布式锁。
基于数据库实现的分布式锁比较简易,绝招在于创建一张锁表,为申请者在锁表里建立一条记录,记录建立成功则获得锁,消除记录则释放锁。
所谓基于缓存,也就是说把数据存放在计算机内存中,不需要写入磁盘,减少了 IO 读写。
Redis 通过队列来维持进程访问共享资源的先后顺序。Redis 锁主要基于 setnx 函数实现分布式锁,当进程通过 setnx 函数返回 1 时,表示已经获得锁。排在后面的进程只能等待前面的进程主动释放锁,或者等到时间超时才能获得锁。
使用 ZooKeeper 可以完美解决设计分布式锁时遇到的各种问题,比如单点故障、不可重入、死锁等问题。虽然 ZooKeeper 实现的分布式锁,几乎能涵盖所有分布式锁的特性,且易于实现,但需要频繁地添加和删除节点,所以性能不如基于缓存实现的分布式锁。
数据、模型(也叫作算法)、算力是人工智能的三大核心。可以说,在一定程度上数据决定了机器学习的上限,而模型为逼近这个上限提供方法,因此数据处理和模型训练是人工智能的关键技术,算力决定了数据处理和模型训练的实用性能,而分布式技术就是解决算力的不二妙招。
主要讲解了Google Borg、Kubernetes、Apache Mesos 三个经典的集群管理系统。
Borg 是 Google 内部使用的集群管理系统,采用了典型的集中式结构,负责提交、调度、开始、重启和管理 Google 运行在其上的所有应用。
Kubernetes 是 Google 开源的容器集群管理系统,是 Borg 的一个开源版本。
Apache 旗下的开源分布式资源管理框架 Mesos ,它被称为是分布式系统的内核,最初由加州大学伯克利分校的 AMPLab 开发,后在 Twitter 得到广泛使用。
Akka 集群
Akka 框架基于 Actor 模型,提供了一个用于构建可扩展的、弹性的、快速响应的应用程序的平台。其中,Actor 是一个封装了状态和行为的对象,它接收消息并基于该消息执行计算。Actor 之间互相隔离,不共享内存,但 Actor 之间可通过交换消息(mail)进行通信(每个 Actor 都有自己的 MailBox)。
Redis 集群
Redis 是一个开源的高性能分布式 key-value 数据库,应用广泛,其特征主要表现为:
支持数据的持久化,可以将内存中的数据保存在磁盘中,重启时可以再次加载并使用;
支持多种数据结构,不仅支持简单的 key-value 类型的数据,同时还提供 list、set、hash 等数据结构的存储;
支持数据的备份,即 Master/Slave 模式的数据备份。
Cassandra 集群
Cassandra 集群的系统架构是基于一致性哈希的完全 P2P 结构,没有 Master 的概念,所有节点都是同样的角色,彻底避免了因为单点问题导致的系统不稳定。Cassandra 集群节点间的状态同步,也是通过 Gossip 协议来进行 P2P 通信的。
单体调度,就是任务和分布式系统中的空闲资源直接进行匹配调度。也就是说,调度器同时管理任务和资源,如果把资源比作“物质文明”,把任务比作“精神文明”,那么单体调度就是“物质文明和精神文明一手抓”。
布式系统中的单体调度是指,一个集群中只有一个节点运行调度进程,该节点对集群中的其他节点具有访问权限,可以搜集其他节点的资源信息、节点状态等进行统一管理,同时根据用户下发的任务对资源的需求,在调度器中进行任务与资源匹配,然后根据匹配结果将任务指派给其他节点。
单体调度器拥有全局资源视图和全局任务,可以很容易地实现对任务的约束并实施全局性的调度策略。
什么是两层调度?
把资源和任务分开调度,也就是说一层调度器只负责资源管理和分配,另外一层调度器负责任务与资源的匹配呢。
两层调度结构对应的就是两层调度器,资源的使用状态同时由中央调度器和第二层调度器管理,中央调度器从整体上进行资源的管理与分配,将资源分配到第二层调度器;再由第二层调度器负责将资源与具体的任务配对,因此第二层调度可以有多个调度器,以支持不同的任务类型。
两层调度器的职责分别是:第一层调度器负责管理资源并向框架分配资源,第二层调度器接收分布式集群管理系统中第一层调度器分配的资源,然后根据任务和接收到的资源进行匹配。
以 Mesos 为基础的分布式资源管理与调度框架包括两部分,即 Mesos 资源管理集群和框架。
一种新的调度器架构被设计了出来。这种架构基本上沿袭了单体调度器的模式,通过将单体调度器分解为多个调度器,每个调度器都有全局的资源状态信息,从而实现最优的任务调度,提供了更好的可扩展性。也就是说,这种调度架构在支持多种任务类型的同时,还能拥有全局的资源状态信息。要做到这一点,这种调度架构的多个调度器需要共享集群状态,包括资源状态和任务状态等。因此,这种调度架构,我们称之为共享状态调度器。
以Omega为例讲解了共享状态调度。
常见的4种计算模式,包括 MapReduce、Stream、Actor 和流水线。
用分治法解决问题的核心步骤是:
分解原问题。将原问题分解为若干个规模较小,相互独立,且与原问题形式相同的子问题。
求解子问题。若子问题规模较小且容易被解决则直接求解,否则递归地求解各个子问题。
合并解,就是将各个子问题的解合并为原问题的解。
MapReduce 分为 Map 和 Reduce 两个核心阶段,其中 Map 对应“分”,即把复杂的任务分解为若干个“简单的任务”执行;Reduce 对应着“合”,即对 Map 阶段的结果进行汇总。
整个 MapReduce 的工作流程主要可以概括为 5 个阶段,即:Input(输入)、Splitting(拆分)、Mapping(映射)、Reducing(化简)以及 Final Result(输出)。
Fork-Join 是 Java 等语言或库提供的原生多线程并行处理框架,采用线程级的分而治之计算模式。它充分利用多核 CPU 的优势,以递归的方式把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器上并行执行,即 Fork 操作。当多个“小任务”执行完成之后,再将这些执行结果合并起来即可得到原始任务的结果,即 Join 操作。
处理流数据任务的计算模式,在分布式领域中叫作 Stream。
在分布式领域中,处理流数据的计算模式,就是流计算,也叫作 Stream。
使用流计算进行数据处理,一般包括 3 个步骤:
第一步,提交流式计算作业。
第二步,加载流式数据进行流计算。
第三步,持续输出计算结果。
Actor 类似于一个“黑盒”对象,封装了自己的状态和行为,使得其他 Actor 无法直接观察到它的状态,调用它的行为。多个 Actor 之间通过消息进行通信,这种消息类似于电子邮箱中的邮件。Actor 接收到消息之后,才会根据消息去执行计算操作。
Actor 模型又是什么呢?Actor 模型,代表一种分布式并行计算模型。这种模型有自己的一套规则,规定了 Actor 的内部计算逻辑,以及多个 Actor 之间的通信规则。在 Actor 模型里,每个 Actor 相当于系统中的一个组件,都是基本的计算单元。
Actor 模型的三要素是状态、行为和消息。
Actor 关键特征:
实现了更高级的抽象。
非阻塞性。
无需使用锁。
并发度高。
易扩展。
计算机中的流水线(Pipeline)技术是一种将每条指令拆分为多个步骤,多条指令的不同步骤重叠操作,从而实现几条指令并行处理的技术。现代 CPU 指令采用了流水线设计,将一条 CPU 指令分为取指(IF)、译码(ID)、执行(EX)、访存(MEM)、回写(WB)五级流水线来执行。
MapReduce 以任务为粒度,将大的任务划分成多个小任务,每个任务都需要执行完整的、相同的步骤,同一任务能被并行执行,可以说是任务并行的一种计算模式;
而流水线计算模式以步骤为粒度,一个任务拆分为多个步骤,每个步骤执行的是不同的逻辑,多个同类型任务通过步骤重叠以实现不同任务的并行计算,可说是数据并行的一种模式。
本地调用通常指的是,进程内函数之间的相互调用;而远程调用,是进程间函数的相互调用,是进程间通信 IPC(Inter-Process Communication)的一种方式。
RPC 与本地调用主要有三点不同:
第一个区别是,调用 ID 和函数的映射。
第二个区别是,序列化和反序列化。
第三个区别是,网络传输协议。
Dubbo 的架构主要包括 4 部分:
服务提供方。服务提供方会向服务注册中心注册自己提供的服务。
服务注册中心。服务注册与发现中心,负责存储和管理服务提供方注册的服务信息和服务调用方订阅的服务类型等。
服务调用方。根据服务注册中心返回的服务所在的地址列表,通过远程调用访问远程服务。
监控中心。统计服务的调用次数和调用时间等信息的监控中心,以方便进行服务管理或服务失败分析等
RMI 是一个基于 Java 环境的应用编程接口,能够让本地 Java 虚拟机上运行的对象,像调用本地对象一样调用远程 Java 虚拟机上的对象。
同步调用和异步调用的区别是,是否等待被调用方执行完成并返回结果。
发布订阅的三要素是生产者、消费者和消息中心,生产者负责产生数据放到消息中心,消费者向消息中心订阅自己感兴趣的消息,当发布者推送数据到消息中心后,消息中心根据消费者订阅情况将相关数据推送给对应的订阅者。
在分布式通信领域中,消息系统一般有两种典型的模式。一种是点对点模式(P2P,Point to Point),另一种是发布订阅模式(Pub/Sub,Publish/Subscribe)。
Kafka 是一种典型的发布订阅消息系统,其系统架构也是包括生产者、消费者和消息中心三部分。
生产者(Producer)负责发布消息到消息中心,比如电子论文的会议方或出版社;
消费者(Consumer)向消息中心订阅自己感兴趣的消息,获得数据后进行数据处理,比如订阅电子论文的老师或学生;
消息中心(Broker)负责存储生产者发布的消息和管理消费者订阅信息,根据消费者订阅信息,将消息推送给消费者,比如论文网站。在 Kafka 中,消息中心本质上就是一组服务器,也可以说是 Kafka 集群。
发布订阅模式的关键特征:
实现了系统解耦,易于维护。
实现了异步执行,避免高负载。
队列是一种具有先进先出特点的数据结构,消息队列是基于队列实现的,存储具有特定格式的消息数据,比如定义一个包含消息类型、标志消息唯一性的 ID、消息内容的一个结构体作为消息数据的特定格式。消息以特定格式放入这个队列的尾部后可以直接返回,并不需要系统马上处理,之后会有其他进程从队列头部开始读取消息,按照消息放入的顺序逐一处理。
消息队列的核心结构:
生产者。生产者会产生消息或数据,并将消息或数据插入到消息队列中。
消息队列。一种具有先进先出特点的数据结构,用于存储消息。
消费者。从消息队列中获取消息或数据,进行相关处理。
RocketMQ
NameServer Cluster,指的是名字服务器集群。这个集群的功能与 Kafka 中引入的 ZooKeeper 类似,提供分布式服务的协同和管理功能,在 RocketMQ 中主要是管理 Broker 的信息,包括有哪些 Broker、Broker 的地址和状态等,以方便生产者获取 Broker 信息发布消息,以及订阅者根据 Broker 信息获取消息。
Producer Cluster,指的是生产者集群,负责接收用户数据,然后将数据发布到消息队列中心 Broker Cluster。那么,生产者按照集群的方式进行部署,好处是什么呢?在我看来,好处可以概括为以下两点:一是,多个 Producer 可以并发接收用户的输入数据,提升业务处理效率;二是,考虑到可靠性问题,如果只有一个 Producer 接收用户输入数据,当这个 Producer 故障后,整个业务就无法运行了。
Consumer Cluster,指的是消费者集群,负责从 Broker 中获取消息进行消费。Consumer 以集群方式进行部署的好处是,提升消费者的消费能力,以避免消息队列中心存储溢出,消息被丢弃。
Broker Cluster,指的是 Broker 集群,负责存储 Producer Cluster 发布的数据,以方便消费者进行消费。
C 代表 Consistency,一致性,是指所有节点在同一时刻的数据是相同的,即更新操作执行结束并响应用户完成后,所有节点存储的数据会保持相同。
A 代表 Availability,可用性,是指系统提供的服务一直处于可用状态,对于用户的请求可即时响应。
P 代表 Partition Tolerance,分区容错性,是指在分布式系统遇到网络分区的情况下,仍然可以响应用户的请求。网络分区是指因为网络故障导致网络不连通,不同节点分布在不同的子网络中,各个子网络内网络正常。
分布式存储系统的核心逻辑,就是将用户需要存储的数据根据某种规则存储到不同的机器上,当用户想要获取指定数据时,再按照规则到存储数据的机器里获取。
这个过程就是分布式存储系统中获取数据的通用流程,顾客、导购和货架组成了分布式存储系统的三要素,分别对应着分布式领域中的数据生产者 / 消费者、数据索引和数据存储。
数据分片技术,是指分布式存储系统按照一定的规则将数据存储到相对应的存储节点中,或者到相对应的存储节点中获取想要的数据,这是一种很常用的导购技术。这种技术,一方面可以降低单个存储节点的存储和访问压力;另一方面,可以通过规定好的规则快速找到数据所在的存储节点,从而大大降低搜索延迟,提高用户体验。
一致性哈希同样是采用哈希函数,进行两步哈希:
1.对存储节点进行哈希计算,也就是对存储节点做哈希映射;
2.当对数据进行存储或访问时,首先对数据进行映射得到一个结果,然后找到比该结果大的第一个存储节点,就是该数据应该存储的地方。我会在下面的内容中,与你详细介绍其中的原理。
一致性哈希是指将存储节点和数据都映射到一个首尾相连的哈希环上,存储节点可以根据 IP 地址进行哈希,数据通常通过顺时针方向寻找的方式,来确定自己所属的存储节点,即从数据映射在环上的位置开始,顺时针方向找到的第一个存储节点。
带有限负载的一致性哈希方法比较适合同类型节点、节点规模会发生变化的场景。
哈希、一致性哈希、带有限负载的一致性哈希,都没有考虑节点异构性的问题。
数据复制是一种实现数据备份的技术。
数据复制技术方法,大体上有三类:
第一类方法,比较注重一致性,比如同步复制技术;
第二类方法,则更注重可用性,比如异步复制技术;
第三类方法,是介于前两者之间的,比如半同步复制技术。
同步复制技术是指,当用户请求更新数据时,主数据库必须要同步到备数据库之后才可给用户返回,即如果主数据库没有同步到备数据库,用户的更新操作会一直阻塞。这种方式保证了数据的强一致性,但牺牲了系统的可用性。
异步复制技术是指,当用户请求更新数据时,主数据库处理完请求后可直接给用户响应,而不必等待备数据库完成同步,即备数据库会异步进行数据的同步,用户的更新操作不会因为备数据库未完成数据同步而导致阻塞。显然,这种方式保证了系统的可用性,但牺牲了数据的一致性。
同步复制技术会满足数据的强一致性,但会牺牲一定的可用性;异步复制技术会满足高可用,但一定程度上牺牲了数据的一致性。介于两者中间的是,半同步复制技术。半同步复制技术的核心是,用户发出写请求后,主数据库会执行写操作,并给备数据库发送同步请求,但主数据库不用等待所有备数据库回复数据同步成功便可响应用户,也就是说主数据库可以等待一部分备数据库同步完成后响应用户写操作执行成功。
缓存技术一般是指,用一个更快的存储设备存储一些经常用到的数据,供用户快速访问。用户不需要每次都与慢设备去做交互,因此可以提高访问效率。
Redis 中与缓存关系最紧密的三个特性:支持多数据结构、支持持久化和主备同步。
第一,Redis 支持多数据结构。
第二,Redis 支持持久化。
第三,Redis 支持主备同步。
负载均衡可以分为两种:
一种是请求负载均衡,即将用户的请求均衡地分发到不同的服务器进行处理;
另一种是数据负载均衡,即将用户更新的数据分发到不同的存储服务器。
轮询策略
轮询策略是一种实现简单,却很常用的负载均衡策略,核心思想是服务器轮流处理用户请求,以尽可能使每个服务器处理的请求数相同。生活中也有很多类似的场景,比如,学校宿舍里,学生每周轮流打扫卫生,就是一个典型的轮询策略。
在负载均衡领域中,轮询策略主要包括顺序轮询和加权轮询两种方式。
随机策略
随机策略也比较容易理解,指的就是当用户请求到来时,会随机发到某个服务节点进行处理,可以采用随机函数实现。这里,随机函数的作用就是,让请求尽可能分散到不同节点,防止所有请求放到同一节点或少量几个节点上。
漏桶策略
漏桶策略适用于间隔性突发流量且流量不用即时处理的场景,即可以在流量较小时的“空闲期”,处理大流量时流入漏桶的流量;不适合流量需要即时处理的场景,即突发流量时可以放入桶中,但缺乏效率,始终以固定速率进行处理。
令牌桶策略
令牌桶策略,也是一个很形象的名字,指的是桶里放着很多令牌,请求只有拿到令牌才能被服务器处理。
分布式故障隔离策略
一类是以系统功能模块为粒度进行隔离。比如,通过系统功能 / 服务划分,将系统分为多个功能 / 服务模块,各个功能 / 服务模块之间实现松耦合,即一个功能 / 服务模块出现故障,不会影响其他功能 / 服务模块,根据功能模块或服务由线程执行还是进程执行,通常分为线程级隔离、进程级隔离。
另一类是,通过资源隔离来实现。比如,系统中各个模块拥有自己独立的资源,不会发生资源争抢,从而大大提升系统性能。根据资源所属粒度,通常包括进程级隔离(比如采用容器隔离)、虚拟机隔离、服务器隔离和机房隔离等。
线程级的故障隔离策略,在生产环境中较为常用,尤其对于单体应用(单进程多线程的应用)。
进程级隔离
一种常用的方式就是,将系统按照功能分为不同的进程,分布到相同或不同的机器中。如果系统的进程分布到不同机器上的话,从资源的角度来看,也可以说成是主机级的故障隔离。因为从另一个层面看,系统确实分布到了不同机器上,当某个机器出现故障时,不会对其他机器造成影响。
资源隔离
简单来说,资源隔离就是将分布式系统的所有资源分成几个部分,每部分资源负责一个模块,这样系统各个模块就不会争抢资源,即资源之间互不干扰。这种方式不仅可以提高硬件资源利用率,也便于系统的维护与管理,可以大幅提升系统性能。
基于历史心跳消息预测故障的策略,也就是我们常说的 φ 值故障检测。
总结:以上学习笔记摘自极客时间聂鹏程老师的《分布式技术原理与算法解析》。通过此专栏的学习,对分布式有了一个大体的了解。之前经常听到一些高大上的词汇:dubbo、kafka、zookeeper等,对于它们背后的原理及产生这种技术的历史发展进程都不太清楚。这个专栏可以说是为我们打下了理论基础,其中一些篇章值得反复阅读与理解。
以下为课程的大纲脑图: