了解分布式的基础知识
千里之行始于足下,打好核心技术的基础,上层的技术再复杂也是围绕核心技术的重用和优化。
分布式技术架构
从组织和业务的架构的垂直方向上,一个完备的分布式系统从底层到顶层可以分为这样四个技术架构层次:
- 分布式资源池:解决资源的分布式和异构性问题,物理资源虚拟化形成资源池便于管理。
- 分布式通信:提供可靠的分布式进程之间的通信的问题(MQ,RPC,订阅发布模式)
- 分布式数据存储:常见的分布式数据库,分布式缓存,分布式文件系统
- 分布式计算:分布式项目的业务应用层面,解决基于分布式的计算问题。
后续为了方便,将这四个层次代称为分布式技术的 Y轴技术
对于整个分布式系统构建和运行,分布式系统构成的每个层都需要在水平方向上解决这四个技术问题:
- 分布式协同:各个节点数据和状态的一致性问题
- 分布式调度:资源和请求的匹配问题,我想要的==我拿到的。
- 分布式高可用:分布式链路追踪,容灾,负载均衡,故障隔离恢复,流量控制
- 分布式部署:参考以前的这篇博客:容器&容器云,微服务容器部署是目前来说比较主流的分布式部署方案。
那这四个技术栈就代称为分布式技术的 X轴技术
分布式体系架构演变过程
- 单机模式:应用,数据库都在一个物理机上
- 数据分布式:将应用和数据库拆分,应用和数据库都在不同机器上,然后对数据库进行拆分(一般指水平拆分),实现对数据的负载均衡
- 任务分布式:对应用进行拆分,将一个大型任务拆分成各种子任务,然后对子任务应用进行分布式部署,提升单个大任务的执行效率。
现在大型业务的分布式方案:数据和任务都分布式拆分和部署,实现分布式集群的高扩展,高可用和高性能。
评价一个分布式系统优劣的指标
分布式系统的设计和单机模式有很大的不同,分布式带来高扩展的同时也会带来很多单机模式中不会出现的问题。不过,对于评判一个系统来说,很多指标是通用的:
- 性能指标 Performance
- 吞吐量:一定时间内系统可以处理的任务数量
- QPS:查询/s,一般作为读操作指标。
- TPS:事务/s,一般作为写操作指标,因为写操作一般和事务原子性挂钩。
- BPS:数据量(Bits)/s,一般作为数据量上的通用指标,可以更统一衡量一个系统的吞吐量,有的时候,事务之间和查询之间的数据量差异还是很大的。
- 响应时间:从用户发起请求到服务端响应的时间,一般是作为服务端前的网关和负载均衡的指标,对于一些实时性要求特别高的系统很重要。
- 完成时间:系统真正完成处理一个任务的时间,对于一些需要并行大量计算的系统来说,这个这个指标海是很重要的。
- 吞吐量:一定时间内系统可以处理的任务数量
- 资源占用 Resource Usage
- 空载资源占用,系统在无任何任务执行时的系统占用,空载资源占用越少,系统设计越轻量,证明利用率高,更加环保。
- 满载资源占用,体现这个系统的满负荷运行能力,同样配置下,满载资源占用下处理的任务月多,系统越好。
- 可用性 Availability
- 一般系统可用性指标计算公式为:系统停止服务时间/系统总的运行时间
- 除开系统外,功能颗粒度的也可以表示为:功能失败次数/总的请求次数
- 对于一个分布式系统,可用性对应系统的容灾指标,因为分布式系统不出问题是不可能的,比如一台机器出问题的可能性只有 0.01%,那一个1000台机器的分布式集群完全不出问题的可能性只有 90% 了,如果完全不做容灾,一台机器导致整个业务损害的概率也太高了。
- 可扩展性 Scalability
- 一个理想的分布式系统是呈现线性扩展性的,最好是增加 10% 的机器就能提升 10% 的性能,尽可能减少大集群带来的边缘衰减效应。
- 扩展性可以从两个方向来看:纵向扩展-水平扩展。分别代表用更高性能的硬件和增加更多的机器。
分布式核心技术通识
这部分是关于分布式的一些基本的核心通识,也是几乎所有分布式系统都会用的技术,先快速了解一下。
分布式互斥和分布式锁
分布式互斥其实本质和进程互斥,线程互斥都是同样的原理,在分布式系统中,运行在不同机器上的应用(进程)会对同一个临界资源有竞争,就形成了分布式互斥。
分布式锁就是为了解决分布式互斥,原理和进程间锁一样,只不过以分布式应用间的锁的形式存在于分布式系统中,解决多个应用间对临界资源的竞争问题。
但是,如果对多线程/多进程熟悉的话,很容易联想到,解决互斥问题肯定不止上锁这样一种,轮训,协商,调度,这些思想也在分布式系统中有应用。
不过和以前我们接触到的互斥问题不同,分布式系统互斥最特别的问题在于,每一台机器没有物理上更上一层的系统帮忙调度,进程间的互斥可以依赖操作系统的调度,线程间的互斥可以依赖进程,但两个分布式的主机之间的互斥就没有物理意义更上一级的系统帮助调度了,这也是我们需要解决的问题。
分布式选举和分布式共识
分布式选举会选举出一个主节点,这个主节点物理意义上和其他节点平级,但是系统意义上,这个主节点可以起到操作系统对于进程,进程对于线程的作用。
同时由于主节点和其他节点其实是物理意义上平级的,也就意味着,任何一个节点都可以成为主节点,这个选出一个主节点的过程就是分布式选举,目前已经有很多算法,Bully,Raft,ZAB 算法,以后开专题聊。
这种所有节点在一起决定某件事,确定某一个状态的过程,就称为分布式共识,分布式共识 的目的是解决系统的一致性问题,所以达成分布式共识的算法也就是一致性算法,目前有:PoW,PoS,DPoS,后续聊。
分布式事务
如果了解单机的数据库事务的话,那分布式事务也不难理解。当我们的数据是分布式的时候,如果一个事务不可避免的和多张表交互,那么这个事务也很容易和多个数据库交互,尤其遇上水平切片的数据库分表,如何保障横跨多个机器的事务的ACID就是分布式事务所需要解决的问题。
但是由于在分布式中严格实现事务ACID的要求太困难了,容易导致系统的可用性大大下降,于是,后面又提出了一种事务:柔性事务。柔性事务不再严格追求 ACID,而是改为遵循 BASE 理论:
- Base Available 基本可用
- Soft State 柔性状态(中间状态)
- Eventual Consistency 最终一致性
表明柔性事务可以接受中间状态的弱一致性,但是保证最终的一致性,放弃了强一致性,用于保证系统的高可用性。
Y::分布式通信
RPC 远程调用
Remote Procedure Call,远程调用,对应的就是本地调用,RPC 的目的就是为了让用户能够像调用本地服务一样去调用远端的服务,达成服务与服务之间分布式部署,但是调用却无感的目的。
一个完整的 RPC 过程:
初始化:服务端注册服务到注册中心,调用端也需要注册到注册中心(涉及接口鉴权等一系列问题),这就是开始初始化时的注册中心和服务发现的过程,一般是服务主动注册,注册中心被动发现。
正式调用:调用端本地调用代理接口,代理将调用参数序列化,从注册中心拿到这次需要调用的服务的网络地址(服务寻址),发起网络调用(可能是自己实现的网络协议),等待返回结果,逆序列化返回结果。
由于 RPC 的过程包含了网络调用,而网络调用一般是默认不可靠的,所以还需要给 RPC 配置超时时间,重试策略,然后尽可能的隐藏网络调用的细节,以达到接近本地调用的体验。
MQ 消息队列
如果说 RPC 以一种同步的通信方式,那么 MQ 就是一种异步的通信方式。通信的发起方称为 Producer,接收方为 Consumer,通过指定的 Topic 将两者对应在一起,发起方将消息发到对应 Topic 的消息队列里面,Consumer 不断从消息队列里面取出消息消费,达到通信的目的,目前常见的 MQ 中间件有 Kafka,RocketMQ,RabbitMQ。
MQ 可以帮助分布式系统实现系统解耦,在面对高峰高并发流量的时候,MQ也可以作为缓冲区实现流量削峰,减轻系统负担,提高响应时间。
Pub/Sub 发布订阅
发布订阅,也是一种广播模式,每一条被广播的消息会送到每一个订阅者手上,而不是像 MQ 那样只要有一个 Consumer 消费了就可以。因此发布订阅模式对消费者组的扩展灵活性没有 MQ 那么高,毕竟如果随随便便上线下线一个订阅者,发布订阅中间件如何保证这次的消息真的所有的订阅者都收到了,所以这种模式也是比较适合固定消费常驻的情况。
目前有多重方案支持发布订阅模式,Redis可以,并且几乎所有的 MQ 都有这个功能。
Y::分布式存储
在学习具体的分布式存储技术之前,我们可以先了解构成分布式存储的三要素:数据生产者/消费者,数据索引,数据存储。
生产者/消费者指的是产生数据和消费数据的一方,这两种模型表示整个分布式存储服务的对象,也决定了整个分布式存储的数据类型:
- 结构化数据,就是关系型数据,使用关系型数据库存储。
- 半结构化数据,有关系但不多,类似Json,html 这种结构性文本,大多呈现出 K-V 数据的样式。
- 非结构化数据,完全没有固定格式的纯文本数据,这个时候可以用 ES 存储。
数据索引和我们常用的 MySQL 里面的索引不同,数据索引特指用于定位到我们想要的分布式存储节点的索引,比如通过一致性hash定位到指定的分片,这个就是一种数据索引。
数据存储就是存储数据的具体实现,依赖于数据的格式类型,比如上文提到的关系型数据库,KV数据库,ES存储这些,都是具体是数据存储实现。
分布式文件系统 GFS
GFS 已经是 2003 年的老东西了,现在的话完全没有什么实用的必要,即便如此,学习这个系统可以更有效帮助我们去了解分布式技术兴起的最初阶段,世界上最牛逼的一群工程师是如何有远见地设计这样一个简单但是高效的系统的。以及在后面的所有的分布式存储的系统设计中,GFS 的思想是如何源远流长的。
GFS设计思想:简单及高效
GFS 最简单的设计莫过于直接用 linux 的文件系统作为自己的数据存储系统来用了,所以你会发现 chunkserver 的所有 chunk 都是直接以文件形式存储在服务器上的。
GFS 采用经典的 Master 架构,Master 主要承担了数据索引(chunk目录)的作用,本身并不保存任何实际数据,而是维护文件和 chunk 之间的映射,并且处理所有的请求。这种架构使得整个集群的管理十分简单高效,坏处就是给 Master 带来了极大的负担。需要注意的是 Master 只存储请求,而不承担数据传输的功能,真实数据还是路由到指定的 ChunkServer 来传输的。
因为所有文件的元数据都存储在 Master 上,为了应对高并发的需求,GFS 选择将数据都维护在内存中,这意味着 master 一旦宕机,所有的数据就会全部丢失,为了保障简单架构下的可用性,Master 会有两个 backup master 来承担数据备份的任务,每次 Master 更新数据都会同步复制给 Backup,以便 Master 一旦下线,backup 就可以直接无缝衔接。
对于大多数文件系统来说,读多写少才是常态,为了分担 Master 节点的读写压力(主要是读请求的压力),Master 节点会有多个分担读请求压力的影子节点 Shadow Master,Shadow 会以异步复制的方式不断努力跟上 Master 的状态,保证高性能的同时也要能忍受其中带来的弱一致性问题。
Shadow 还有一个用处,那就是当 Master 挂掉的时候,在 Backup 正在恢复的过程中,读功能几乎不受到影响,只是一致性可能有点小问题,算的上是很高可用了。
对于 ChunkServer,也是一式三份,一个主数据,两个备份,异步写入,数据不一致的时候以主备份为准,同时两个副备份也用作只读节点分担主节点压力。
GFS设计思想:适合才最好
GFS 诞生的年代廉价计算机性能还没有我们现在用的手机的性能好,GFS 又是一个搭载在大量廉价服务器上的分布式大型分布式系统,所以很多地方会为了当时的运行环境做特意适配的设计,如果是现在的话,云服务器基本不用我们开发者去考虑这些事情了。
论文说当时服务器上的CPU都是双核1.45GHz的奔腾3处理器,250nm制程,甚至比不上现在千元手机的处理器。
从很多意义上来说,GFS 是不可靠的,但是当时 GFS 主要用于对 Goggle 搜索引擎的爬取支持,在这场景下,GFS 反而能完成的很出色,现在也就简单聊聊 GFS 做了哪些,因为一部分设计现在已经不够用了。
在 GFS 诞生的年代,硬盘全都是机械硬盘,机械硬盘对于随机读写的性能太差,加上随机读写的确定性问题实现比较复杂(一致性倒是容易保证),于是 GFS 支持的最好的就是追加写入操作,一个 chunk 64MB,最多一次追加写入 16MB,剩余空间不够就塞满空数据后新开 chunk,这样保证最多也就浪费 1/4 的 chunk 空间,并且做了至少一次的保证,即保证数据至少有一次一致且完整地存入了 ChunkServer 的主备份和两个副备份中。至于写入失败的数据,作为脏区放着不管就行。
同时,为了解决网络瓶颈问题,GFS 使用了一个很重要的设计思想:数据流和控制流分离。控制流指的是通过 Master 的控制指令流,显然我们写入的时候并不希望也将数据流的网络带宽压力交给 Master 来分配,所以 Master 只返回指定 ChunkServer 的路由,数据流由客户端和 ChunkServer 直接传输。
同时数据流通过流式传输尽量利用网络带宽,客户端不需要把数据传给每一个 ChunkServer,而是传输到最近的 ChunkServer 后,由 ChunkServer 再和其他的副本同步,避免对 ChunkServer 的网络带宽的浪费。
分布式数据库-以MySQl为🌰
后面专门写一篇,略过
分布式缓存-以Redis为🌰
后面专门写一篇,略过
Y::分布式计算
分布式计算可以简单理解成如何设计分布式任务,面对不同的场景,分布式任务的设计也有所不同,下面会介绍一些典型的基本涵盖所有分布式任务模型的计算设计框架。
MapReduce:分而治之
MapReduce 应该是每一个大数据新手的第一个实践项目,最经典的用法就是对超大文本(比如一个 100MB 纯文本)进行分词计数,WordCount 也是一个很适合学习 MapReduce 框架的项目,他几乎覆盖了 MR 的所有特性并且还十分易懂,所以接下来也是以 WordCount(WC) 为例去一步步分析 MR 是如何搭建运行的。
WC 产生于这样一个问题,假如我们呢有一个 1GB 的纯英文文本,希望对里面的词进行计数然后排序,我们需要怎么做?
1GB 纯英文文本文件大概有2亿个单词,显然不能靠单机性能,那我们把文本分割成 50MB 的小chunk,然后用20台机器并行计数,这就是一个分布式任务,接下来解决数据汇总问题。
如果同步汇总到同一个数据库,,二十台机器在更新同一个词的计数上难免会有竞争问题,并且如果我们希望通过计数排序,那么计数应该是一个索引,在数据库中反复修改索引带来的性能瓶颈也不是我们能接受的。
那不如各自记录到自己的数据库,最后再汇总的到一起,也是一个看起来不错的选择。但这时,同一个词的记录往往会被重复存放在不同的数据库中,最后汇总的时候需要从不同的数据库中反复拿到同一个词的记录,不够优雅。
如果思考过这些问题,一些解决方案就开始有眉目了:尽量让同样的词 map 到同一个数据存储中,最后直接聚合一下再插入到主库,就不会存在一个词的计数索引被反复修改的问题,这就是 MapReduce 的想法。
MapReduce 将整个任务分为了三层流程结构,Worker - Shuffle - Reducer,不过有的时候我们也说是两层结构,这些都没太大所谓,两层结构就是 Mapper - Reducer,这个时候的 Mapper 相当于把前置 Worker 的工作也做了,没有太大的区别,下面还是以三层结构来解析,因为这三层结构就正好是 ETL 数据处理结构,我们会更加熟悉。
ETL:Extra-Transform-Load,是一类符合数据提取,转化,录入的数据处理流程的简称
Worker:提取数据构建KV索引
Worker 承担 E 的职责,将数据提取,并且做一些过滤拦截的预处理,比如在 WC 项目中,我们希望一个 Worker 只提取 10MB 的文本,并且完成分词,过滤一些没有什么用的单词(比如a,an,you,the这些),并且生成 KV 数据流交给 Shuffle,在 WC 的项目中,KV数据大概是这样的:chunk_1=(cake,10) 表示 chunk_1 中 cake 的计数为 10,这个时候,chunk 是索引,(cake,10) 是value,这点很重要。
Shuffle:倒排索引并分配
Shuffle 承担 T 的职责,要干的事就只有一个,同时也是 MapReduce 中最重要的一个思想:倒排索引。
简单解释一下倒排索引,这个其实是一个翻译上的不当人导致的难以理解的名词,其实比较合适的理解应该是“键值反转”,英文原称:Inverted Index. 就是把 key 变成 value,把 value 变成 key。
Worker 传过来键值对 file_1=(word,count),Shuffle 会将这个键值对反转成 word=(file_1,count),此时 word 和 file 的 KV 关系就反转了,反转之后,Shuffle 则根据 word 的索引进行分片存储,保证同一个词能被集中存放在一起,方便处理。
Reducer:聚合
Reducer 承担 L 的职责,作用就是聚合函数,在 WC 项目中,word 作为索引已经被 Shuffle 到了一起,Reducer 再去把这些数据按照任务设计聚合起来,比如求和,计算在文章中的分布位置,等等。
如果以整体的目光去分析 MapReduce 的框架,就会发现 Worker 和 Reducer 所依赖的索引是完全反转的,Worker 会根据 file_chunk 作为索引去提取数据,处理产生的值为 word 和 count,而 Reducer 则完全反过来,以 word 为主索引去处理 file_chunk 和 count,在处理的过程中再生成的键值对就不是我们谈论的范围了。
题外话
其实 MapReduce 的工作很适合用 SQL 语句来表达出来,以 WC 为例,假设 Worker 的分词预处理已经完成,并且所有的内容都存放在同一个表里(显然几个GB的数据存同一张 SQL 表是不可能的,这里只是假设)我们的 Shuffle-Reduce 的过程就可以表示为
SELECT
SUM(count) -- reduce
FROM worker_kv_chunk_1 AS c1
FULL JOIN worker_kv_chunk_2 AS c2 ON c1.word = c2.word
FULL JOIN worker_kv_chunk_3 AS c2 ON c1.word = c2.word
...
GROUP BY word -- shuffle 倒排索引
这里提到 SQL 也不是一时兴起,而是
Apache Hive 是一个基于 Hadoop 的数据仓库工具,主要用于处理和分析存储在 HDFS(Hadoop Distributed File System)中的大规模数据集。Hive 提供了一种类 SQL 的查询语言,称为 HiveQL,使得用户可以用熟悉的 SQL 语法对大数据进行查询和分析,而不需要编写复杂的 MapReduce 程序。
写的够多了,下面的内容先留个坑,等到下一篇博客再继续分享
- Y::分布式算法
- Stream:不尽长江滚滚来
- Actor:排队等叫号
- 流水线:我们称其为高效
- X::分布式协同
- X::分布式调度
- 单体调度:Brog->K8S
- 两层调度:Mesos&上万节点集群
- 共享状态调度:Omega
- X::分布式高可用
- 链路追踪
- 负载均衡
- 流量控制
- 故障隔离与恢复