*本文为「码上观世界」原创内容
今日政论:人类大脑不过3斤重,却能迸发出无情无尽的想法,作为个人,一个人对外部世界的影响微乎其微,但是如果将很多大脑联合起来,其造成的影响却能让整个太阳系不得安宁。任何人都无法感知对方的所思所想,这就造成人与人缺乏完全的互信,在一个国家内部,可以有不同的层级的组织对个体施加约束,以防出现不测行为。然而到达国家跟国家之间的层面,由于缺少有力的约束,国与国不得不为可能发生的事情提前做好预案,比如增强国防能力,于是国与国之间你追我赶,将能力差距限定在安全范围内。造成这种结果的机制是一把双刃剑,一方面提升了人类探索自然的能力、拓宽了人类活动的边界,另一方面也将人类总体推向了毁灭的边缘。目前无人得知,这种机制是一种系统bug,还是一种造物主预设的陷阱,亦或是一种正常的打怪升级步骤。目前来看,三种情况都有可能,如果是前两者,那么整个人类(包括绝大部分生物系统)将会在地球系统的重启后从零开始进化。如果是正常的进化步骤,那么能够拯救人类的只有人类自己,从一个国家内部的发展经验来看,能够让全体民族放下武器、统一思想只有通过全民族的政权和思想统一,对全球国家来讲,这种可能性只有当地球面临一个更大更优先级的外部威胁,比如自然灾难、外星文明入侵等,所有国家才能够成立真正意义上的联合国政权,届时当所有国家交出军事、最高行政权归联合国所有才能彻底解决内耗的怪圈。
由于大数据应用场景的复杂性:既包括针对大量历史数据的分析探查也包括物联网等场景的实时分析场景,以及为满足这些需求场景涉及到的数据存储、数据计算和数据查询等诞生的不同技术栈,大数据开发设计总体上是围绕着如何保证系统的可靠性、可扩展性和可维护性而展开,本文将这些常用的开发设计实践总结为10大模式,分别介绍如下:
1 分治与扩展
分布式系统采用分治策略将大问题分解为可容易解决的小问题,从而发挥分布式集群的并行计算能力。分治策略是实现分布式的基础。下面从分布式存储、分布式计算和分布式查询3个场景来解释:
1.1 分布式存储
HDFS分布式存储通过主从架构,引入NameNode和DataNode,将大文件拆分为大小不超过128M的文件块,从而将大文件分布在不同的DataNode上,然后在NameNode维护相应文件包含的块数据文件路径等元信息。客户端通过NameNode读写相应文件。
1.2 分布式计算
MapReduce将大任务分解成Map和Reudce两个阶段,借住分布式集群的并行计算能力,将多个Map子任务并行计算的中间结果通过Reduce过程得到最终结果。MapReduce既是一个并行计算与运行软件框架(Software Framework)又是一个并行程序设计模型与方法(Programming Model & Methodology)。作为并行计算软件框架,它能自动完成计算任务的并行化处理,自动划分计算数据和计算任务,在集群节点上自动分配和执行任务以及收集计算结果,将数据分布存储、数据通信、容错处理等并行计算涉及到的很多系统底层的复杂细节交由系统负责处理,大大减少了软件开发人员的负担。作为并行程序设计模型与方法,它借助于函数式程序设计语言Lisp的设计思想,提供了一种简便的并行程序设计方法,用Map和Reduce两个函数编程实现基本的并行计算任务,提供了抽象的操作和并行编程接口,以简单方便地完成大规模数据的编程和计算处理 。
1.3 分布式查询
不同于OLTP,OLAP不特别关心对数据进行输入、修改等事务性处理,而是关心对已有的大量数据进行多维度的、复杂的分析,基于共享全部资源,如总线,内存和I/O系统等的对称多处理器结构SMP(Symmetric Multi-Processor)由于每个 CPU 必须通过相同的内存总线访问相同的内存资源,随着 CPU 数量的增加,内存访问冲突将迅速增加,最终阻碍了其扩展能力。为解决SMP 在扩展能力上的限制,NUMA(Non-Uniform Memory Access)将多个CPU模块集成到一个服务器内部,MPP (Massively Parallel Processing),大规模并行处理系统由许多松耦合的处理单元组成的,每个单元内的CPU都有自己私有的资源,如总线,内存,硬盘等。在每个单元内都有操作系统和管理数据库的实例复本。
大规模并行分析(MPP)数据库(Analytical Massively Parallel Processing (MPP) Databases)是针对分析工作负载进行了优化的数据库:聚合和处理大型数据集。MPP数据库往往是列式的,因此MPP数据库通常将每一列存储为一个对象,而不是将表中的每一行存储为一个对象(事务数据库的功能)。这种体系结构使复杂的分析查询可以更快,更有效地处理。
这些分析数据库将其数据集分布在许多机器或节点上,以处理大量数据(因此得名)。这些节点都包含自己的存储和计算功能,从而使每个节点都可以执行查询的一部分。这类系统比较多,如Impala、PrestoDB、Clickhouse、Greenpulm、Druid等,其中Impala、PrestoDB可以基于共享存储模式进行,以Impala为例描述其查询过程:
1. 注册&订阅:当Impala启动时,所有Impalad节点会在Impala State Store中注册并订阅各个节点最新的健康信息以及负载情况。2. 提交查询:接受此次查询的ImpalaD作为此次的Coordinator,对查询的SQL语句进行分析,生成并执行任务树,不同的操作对应不同的PlanNode,如:SelectNode、 ScanNode、 SortNode、AggregationNode、HashJoinNode等等。
3. 获取元数据与数据地址:Coordinator通过查询数据库,或者HDFS文件获取到此次查询的数据库所在的具体位置,以及存储方式的信息
4. 分发查询任务:执行计划树里的每个原子操作由Plan Fragment组成,通常一句SQL由多个Fragment组成。Coordinator节点根据执行计划以及获取到的数据存储信息,通过调度器将Plan Fragment分发给各个Impalad实例并行执行。
5. 汇聚结果:Coordinator节点会不断向各个Impalad执行子节点获取计算结果,直到所有执行计划树的叶子节点执行完毕,并向父节点返回计算结果集。
6. Coordinator节点即执行计划数的根节点,汇聚所有查询结果后返回给用户。查询执行结束,注销此次查询服务。
从架构来看, NUMA 与 MPP 具有许多相似之处:它们都由多个节点组成,每个节点都具有自己的 CPU 、内存、 I/O ,节点之间都可以通过节点互联机制进行信息交互。那么它们的区别在哪里?通过分析下面 NUMA 和 MPP 服务器的内部架构和工作原理不难发现其差异所在。
首先是节点互联机制不同, NUMA 的节点互联机制是在同一个物理服务器内部实现的,当某个 CPU 需要进行远地内存访问时,它必须等待,这也是 NUMA 服务器无法实现 CPU 增加时性能线性扩展的主要原因。而 MPP 的节点互联机制是在不同的 SMP 服务器外部通过 I/O 实现的,每个节点只访问本地内存和存储,节点之间的信息交互与节点本身的处理是并行进行的。因此 MPP 在增加节点时性能基本上可以实现线性扩展。
其次是内存访问机制不同。在 NUMA 服务器内部,任何一个 CPU 可以访问整个系统的内存,但远地访问的性能远远低于本地内存访问,因此在开发应用程序时应该尽量避免远地内存访问。在 MPP 服务器中,每个节点只访问本地内存,不存在远地内存访问的问题。
2 读写分离
读写分离解决的是高并发写操作加锁导致的性能下降问题,作为一种编程思想,体现在JDK中是Copy-On-Write写时复制容器,它支持并发的读,如果是写操作,那么会创建一个新的容器,在新的容器中完成写操作,在这个过程中,读操作依然读的是旧容器中的值。完成写操作之后,还会把新容器的引用指向原有容器的引用。这样做的好处就是,可以支持并发的读,而不需要加锁,因为当前容器不会添加或者删除元素。所以Copy-On-Write容器是一种读写分离容器,体现了读写分离的思想。广泛应用在系统软件架构上,如DDD领域中的CQRS。CQRS本身只是一个读写分离的思想,全称是:Command Query Responsibility Segregation,即命令查询职责分离。一个命令表示一种意图,表示命令系统做什么修改,命令的执行结果通常不需要返回;一个查询表示向系统查询数据并返回。另外一个重要的概念就是事件,基本对应DDD中的领域事件。CQRS架构的核心出发点是将整个系统的架构分割为读和写两部分,从而方便我们对读写两端进行分开优化。架构图如下:
从图上可以看到,当 command 系统完成数据更新的操作后,会通过「领域事件」的方式通知 query 系统。query 系统在接受到事件之后更新自己的数据源。所有的查询操作都通过 query 系统暴露的接口完成。在数据库领域,Mysql、Redis、Mongodb等都通过事件溯源方式实现主从复制从而达到读写分离的应用目标。
3 冗余备份与一致性
在分布式系统中,为解决由于机器硬件故障如磁盘损坏等导致的数据丢失以及由于网络不通、系统宕机造成的服务不可用等问题,通常对数据或者服务冗余备份。
3.1 数据备份
比如下图所示的Elastic Search冗余备份数据:
图例中,对Index1划分的3个主分片存储在三个不同的节点上(每个分片无副本),Index2的2个主分片数据分布在两个节点上(每个分片各有一个副本分片)。副本(Replica)的目的有三个:
1. 保证服务可用性:当设置了多个Replica的时候,如果某一个Replica不可用的时候,那么请求流量可以继续发往其他Replica,服务可以很快恢复开始服务。
2. 保证数据可靠性:如果只有一个Primary,没有Replica,那么当Primary的机器磁盘损坏的时候,那么这个Node中所有Shard的数据会丢失,只能reindex了。
3. 提供更大的查询能力:当Shard提供的查询能力无法满足业务需求的时候, 可以继续加N个Replica,这样查询能力就能提高N倍,轻松增加系统的并发度。
数据冗余备份已经成为大数据系统设计的标配,如hdfs、mongodb、kafka以及云数仓产品redshift等。但是冗余备份也带来了一些问题,比如对ES来说:
1. Replica带来成本浪费。为了保证数据可靠性,必须使用Replica,但是当一个Shard就能满足处理能力的时候,另一个Shard的计算能力就会浪费。
2. Replica带来写性能和吞吐的下降。每次Index或者update的时候,需要先更新Primary Shard,更新成功后再并行去更新Replica,再加上长尾,写入性能会有不少的下降。
3. 当出现热点或者需要紧急扩容的时候动态增加Replica慢。新Shard的数据需要完全从其他Shard拷贝,拷贝时间较长。
4. 容易造成数据不一致,特别是当主分片数据写入完成,但是在写副本过程中出现中断,造成不同副本的数据不一致。
3.2 服务备份
系统宕机等导致的服务不可用等问题,通常对服务冗余备份,典型的应用场景是Hadoop HA高可用。在Hadoop中,最重要的两种组件应用:HDFS NameNode和YARN Manager。两者的HA实现方式类似,但前者更为复杂。因为主、备NameNode在实现高可用的同时维护了NameNode的Editlog信息,为防止Editlog数据不一致,除了使用NFS共享存储之外,HDFS引入了更优的 Journal Node来实现Editlog共享。Journal Node基于Quorum,Active NameNode只需要保证写入大多数Journal Node节点成功,就认为本次写入成功,Standby NameNode在从Journal Node同步数据时,也只需要保证读取大多数Journal Node节点成功,就认为读取成功。
Quorum机制是“抽屉原理”的一个应用。定义如下:假设有N个副本,更新操作需要至少在W个副本中更新成功,才认为此次更新操作成功。读操作需要至少读R个副本才认为读取成功(读取到更新的数据),那么需要满足条件 W+R>N ,即W和R有重叠,一般情况下 满足 W+R=N+1即可。假设系统中有5个副本,W=3,R=3,初始时数据版本为(V1,V1,V1,V1,V1),当某次更新操作在大多数(如3个)副本上成功后,就认为此次更新操作成功。数据变成:(V2,V2,V2,V1,V1),因此,最多只需要读大多数(如3个)副本成功,一定能够读到最新版本V2的数据。因为Quorum机制不是强一致性,此时读到的最新数据不能确定是成功写入的版本数据,还需要保证读取V2的副本数达到大多数(3个)。
4 故障恢复与转移
故障恢复与转移分别对应有状态和无状态两种应用场景,有状态的应用场景常见于主从系统架构的主节点元信息存储,比如HDFS NameNode,以及计算引擎的中间运行状态信息,如Flink状态数据。故障恢复与转移都是实现容错机制的方式。故障恢复实现方式通常有数据库系统的Redo log,No SQL系统中的 write ahead log,以及Flink等系统中使用Checkpoint机制,Flink 任务状态可以理解为实时任务计算过程中,中间产生的数据结果,同时这些计算结果会在后续实时任务处理时,能够继续进行使用。实时任务的状态可以是一个聚合结果值,比如 WordCount 统计的每个单词的数量,也可以是消息流中的明细数据。Flink 任务状态整体可以划分两种:Operator 状态和 KeyedState。常见的 Operator 状态,比如 Kafka Topic 每个分区的偏移量。KeyedState 是基于 KeyedStream 来使用的,所以在使用前,你需要对你的流通过 keyby 来进行分区,常见的状态比如有 MapState、ListState、ValueState 等。
下面是一个实时计算奇数和偶数的任务的示例,假如输入的流来自于 Kafka ,那么 Kafka Topic 分区的偏移量是状态,所有奇数的和、所有偶数的和也都是状态。当应用异常重启,就可以从状态数据中恢复,避免重复计算。
无状态的应用场景常见于主从架构中的工作节点,比如MapReduce框架中的失败任务,可以在其它节点重新运行,而不需要考虑上次的执行情况,以及分布式DAG任务调度系统的异常重跑任务。
5 解耦与集成
解耦是为了让系统更具有扩展性和可维护性,将整个系统划分为独立的组件,集成是将独立的组件连接起来实现完整的功能,比较有代表的系统比如Apache Airflow。Airflow是一个分布式DAG任务调度系统,由以下基本组件组成:
Metadata Database:Airflow 使用 SQL Database 存储 meta 信息。比如 DAG、DAG RUN、task、task instance 等信息。
Scheduler:Airflow Scheduler 是一个独立的进程,通过读取 meta database 的信息来进行 task 调度,根据 DAGs 定义生成的任务,提交到消息中间队列中。
Webserver:Airflow Webserver 也是一个独立的进程,提供 web 端服务, 定时生成子进程扫描对应的 DAG 信息,以 UI 的方式展示 DAG 或者 task 的信息。
Worker:Airflow Worker 是独立的进程,分布在相同 / 不同的机器上,是 task 的执行节点,通过监听消息中间队列领取并且执行任务。
Queue:Queue也称为Broker, 用于存储待调度的任务,支持不同的实现,如Mysql、Redis、Kafka。
其基本流程为scheduler和webserver分别通过自动和手动方式将待调度任务消息投递到broker,worker从broker拉取待调度任务并根据任务类型决定在本地执行还是提交到远程集群执行,任务运行状态持久化到metadata存储库。scheduler和webserver也会使用metadata任务运行状态数据用于调度和UI可视化。
除了上述基本组件之外,Airflow利用其强大的系统集成能力,将分布式调度、计算引擎、查询系统、数据库系统、分布式消息系统等整合在一起,实现任意类型的任务调度功能。在资源管理上,Airflow还可以借用Mesos、Kubernetes等实现资源调度功能。
另外,为了解决单体应用庞大难以维护的问题,按照单一职责划分原则,将系统拆分为更小的系统,组成多个微服务,微服务之间通过http、rpc等方式通讯,在微服务外围提供通用的流控、服务发现与治理、链路追踪、负载均衡等功能,将这些业务功能和基础功能整合一起组成完整的应用系统。
6 精确计算与模糊计算
在一些大数据计算场景,比如计算某元素是否存在指定集合中,以及计算某集合中不同元素的个数,如果采用常规方法,将会带来很大的存储和计算性能损失,而如果用近似计算的方法,在能容忍的误差范围内提供快捷的响应结果,无疑是很有吸引力的,这类方法主要有两种布隆过滤器和HyperLogLog。前者主要用于查找大集合中是否存在某元素,后者主要用于基数统计。
6.1 布隆过滤器
布隆过滤器(Bloom Filter)的核心实现是一个超大的位数组和几个哈希函数。假设位数组的长度为m,哈希函数的个数为k
以上图为例,具体的操作流程:假设集合里面有3个元素{x, y, z},哈希函数的个数为3。首先将位数组进行初始化,将里面每个位都设置位0。对于集合里面的每一个元素,将元素依次通过3个哈希函数进行映射,每次映射都会产生一个哈希值,这个值对应位数组上面的一个点,然后将位数组对应的位置标记为1。查询W元素是否存在集合中的时候,同样的方法将W通过哈希映射到位数组上的3个点。如果3个点的其中有一个点不为1,则可以判断该元素一定不存在集合中。反之,如果3个点都为1,则该元素可能存在集合中。注意:此处不能判断该元素是否一定存在集合中,可能存在一定的误判率。可以从图中可以看到:假设某个元素通过映射对应下标为4,5,6这3个点。虽然这3个点都为1,但是很明显这3个点是不同元素经过哈希得到的位置,因此这种情况说明元素虽然不在集合中,也可能对应的都是1,这是误判率存在的原因。
6.2 HyperLogLog
HyperLogLog是一种高效的(占用空间低,几乎是常数级别的存储空间)、非精确的数据去重算法,能通过扫描一遍源数据流即可估算出数据基数大小。这里只介绍其背后的数学原理。
伯努利过程是一个由有限个或无限个的独立随机变量 X1, X2, X3 ,..., 所组成的离散时间随机过程,其中 X1, X2, X3 ,..., 满足如下条件:
对每个 i, Xi 等于 0 或 1; 对每个 i, Xi = 1 的概率等于 p. 换言之,伯努利过程是一列独立同分布的伯努利试验。每个Xi 的2个结果也被称为“成功”或“失败”。所以当用数字 0 或 1 来表示的时候,这个数字被称为第i个试验的成功次数。
在n次独立重复的伯努利试验中,设每次试验中事件A发生的概率为p。用X表示n重伯努利试验中事件A发生的次数,则X的可能取值为0,1,…,n,且对每一个k(0≤k≤n),事件{X=k}即为“n次试验中事件A恰好发生k次”,随机变量X的离散概率分布即为二项分布(Binomial Distribution)。
在概率论和统计学中,二项分布是n个独立的成功/失败试验中成功的次数的离散概率分布,其中每次试验的成功概率为p。这样的单次成功/失败试验又称为伯努利试验。如果设p=1/2,那么:
并且
由于每个伯努利过程相互独立,于是
同理,
于是可推导n次过程中至少有一次实验次数为k的概率:
由上述分析可知当伯努利过程次数n远远大于2的k次方时,没有一次过程实验次数大于k的概率几乎为0。当伯努利过程次数n远远小于2的k次方时,至少有一次过程实验次数等于k的概率几乎为0。
如果将上面描述做一个对应:一次伯努利过程对应一个元素的比特串,反面对应0,正面对应1,投掷次数k对应第一个“1”出现的位置,我们就得到了下面结论:
上面只是大致描述HyperLogLog算法背后的数学原理,在实际应用中还需要结合分桶和调和平均等方法来减少计算误差。
7 计算与存储分离
存储与计算分离方案可以追溯到CPU控制单元和内存单元的冯诺依曼体系机构,这可以看做该原则的微观实现,而在大数据领域,代表性的系统架构非AWS EMR莫属,比如AWS引入共享S3对象存储系统,支持不同的计算引擎,使得计算和存储能够独立扩容和升级,避免了计算存储耦合带来的一系列问题。
而在存算耦合时期普遍采用的MPP(Massive Parallel Processing)架构,计算存储共享一个节点,每个节点有自己独立的CPU、内存、磁盘资源,互相不共享。数据经过一定的分区规则(hash、random、range),打散到不同的节点上。处理查询时,每个节点并行处理各自的数据,互相之间没有资源争抢,具备比较好的并行执行能力。这种将存储资源、计算资源紧密耦合的架构,不太容易满足云时代不同场景下的不同workload需求。例如数据导入类的任务,往往需要消耗比较大的IO、网络带宽,而CPU资源消耗不大。而复杂查询类任务往往对CPU的资源消耗非常大。因此面对这两种不同的workload,在选择资源规格时,需要结合不同的workload分别做不同的类型选择,也很难用一种资源规格同时满足这两种类型。特别是当对集群扩容时,也会引发数据的reshuffle,这会消耗比较大的网络带宽、以及CPU资源。即便是基于云平台构建的数据仓库,在查询低峰期时,也无法通过释放部分计算资源降低使用成本,因为这同样会引发数据的reshuffle。这种耦合的架构,限制了数据仓库的弹性能力。
而通过分离存储资源、计算资源,可以独立规划存储、计算的资源规格和容量。这样计算资源的扩容、缩容、释放,均可以比较快完成,并且不会带来额外的数据搬迁的代价。存储、计算也可以更好的结合各自的特征,选择更适合自己的资源规格和设计。
8 数据建模与分层架构
数据仓库之父比尔·恩门(Bill Inmon)在1991年出版的“Building the Data Warehouse”(《建立数据仓库》)一书中所提出的定义被广泛接受——数据仓库(Data Warehouse)是一个面向主题的(Subject Oriented)、集成的(Integrated)、相对稳定的(Non-Volatile)、反映历史变化(Time Variant)的数据集合,用于支持管理决策(Decision Making Support)。因此数据仓库的构建必须要达到如下目标:
成本:模型设计者要平衡性能和成本要素对数据模型的影响,现有海量大数据情况下,以保障业务和性能为前提,合理使用数据模型方案和存储策略,尽量消除不必要的数据复制与冗余。
性能:模型设计者需要兼顾模型刷新性能开销、产出时间和访问性能。
数据一致性及数据互通:各个数据模型或者数据表之间必须保障数据输出的一致性,相同粒度的相同数据项(指标、维度)应具有相同的字段名称和业务描述,不同算法的业务指标应显性化区分。
数据质量:数据公共层模型需要屏蔽上游垃圾数据源,一方面要保障数据本身的高质量,减少数据缺失、错误、异常等情况的发生;另一方面要保障其对应的业务元数据的高质量,数据有明确的业务含义,为数据提使用者供正确的指引。
易用:在保障以上目标的前提下,数据用户能从业务角度出发快速找到所需数据;能较快的掌握模型的适用场景和使用方法;能相对便捷获取数据。
实现上述目标,设计良好的数据模型是前提,数据模型设计需要遵守以下基本原则:
高内聚和低耦合:软件设计方法论中的高内聚和低耦合原则同样适用于数据建模,这主要从数据业务特性和访问特性两个角度来考虑:将业务相近或者相关的数据、粒度相同数据设计为一个逻辑或者模型;将高概率同时访问的数据放一起,将低概率同时访问的数据分开存储。
核心模型与扩展模型分离:建立核心模型与扩 展模型体系,核心模型包括的字段支持常用核心的业务,扩展模型包括的字段支持个性化或是少量应用的需要,必要时让核心模型与扩展模型做关联,不能让扩展字段过度侵入核心模型,破坏了核心模型的架构简洁性与可维护性。
公共处理逻辑下沉及单一:越是底层公用的处理逻辑更应该在数据调度依赖的底层进行封装与实现,不要让公共的处理逻辑暴露给应用层实现,不要让公共逻辑在多处同时存在。
成本与性能平衡:适当的数据冗余换取查询和刷新性能,不宜过度冗余与数据复制。
数据可回滚(数据生成支持幂等性):处理逻辑不变,在不同时间多次运行数据结果确定不变。
一致性:相同的字段在不同表字段名相同,字段值相同。
命名清晰可理解:表命名规范需清晰、一致,表名需易于下游理解和使用。
当前主流建模方法为:ER模型、维度模型。
1、ER模型常用于OLTP数据库建模,应用到构建数仓时更偏重数据整合, 站在企业整体考虑,将各个系统的数据按相似性一致性、合并处理,为数据分析、决策服务,但并不便于直接用来支持分析。缺陷:需要全面梳理企业所有的业务和数据流,周期长,人员要求高。
2、维度建模是面向分析场景而生,针对分析场景构建数仓模型;重点关注快速、灵活的解决分析需求,同时能够提供大规模数据的快速响应性能。针对性强,主要应用于数据仓库构建和OLAP引擎低层数据模型。优点:不需要完整的梳理企业业务流程和数据,实施周期根据主题边界而定,容易快速实现demo,而且相对来说便于理解、提高查询性能、对称并易扩展。
维度模型是目前最常用的建模方法,以阿里倡导的分层建模为例,将维度建模的结果以分成形式呈现为以下结构:
1. 操作数据层( ODS ):把操作系统数据几乎无处理地存放在数据仓库系统中。包括全量和增量同步的结构化数据、经过处理后的非结构化数据以及根据数据业务需求及稽核和审计要求保存历史数据、清洗数据。
2. 数据公共层CDM(Common Data Model,又称通用数据模型层),包括DIM维度表、DWD和DWS,由ODS层数据加工而成。主要完成数据加工与整合,建立一致性的维度,构建可复用的面向分析和统计的明细事实表,以及汇总公共粒度的指标。
公共维度层(DIM):基于维度建模理念思想,建立整个企业的一致性维度。降低数据计算口径和算法不统一风险。公共维度层的表通常也被称为逻辑维度表,维度和维度逻辑表通常一一对应。
公共汇总粒度事实层(DWS):以分析的主题对象作为建模驱动,基于上层的应用和产品的指标需求,构建公共粒度的汇总指标事实表,以宽表化手段物理化模型。构建命名规范、口径一致的统计指标,为上层提供公共指标,建立汇总宽表、明细事实表。
明细粒度事实层(DWD):以业务过程作为建模驱动,基于每个具体的业务过程特点,构建最细粒度的明细层事实表。可以结合企业的数据使用特点,将明细事实表的某些重要维度属性字段做适当冗余,即宽表化处理。
3. 数据应用层ADS(Application Data Service):存放数据产品个性化的统计指标数据。根据CDM与ODS层加工生成。
上述分层架构对数据有一个更加清晰的掌控,详细来讲,主要有以下优势:
清晰数据结构:每一个数据分层都有它的作用域,这样我们在使用表的时候能更方便地定位和理解。
数据血缘追踪:简单来讲可以这样理解,我们最终给业务诚信的是一能直接使用的张业务表,但是它的来源有很多,如果有一张来源表出问题了,我们希望能够快速准确地定位到问题,并清楚它的危害范围。
减少重复开发:规范数据分层,开发一些通用的中间层数据,能够减少极大的重复计算。
把复杂问题简单化。讲一个复杂的任务分解成多个步骤来完成,每一层只处理单一的步骤,比较简单和容易理解。而且便于维护数据的准确性,当数据出现问题之后,可以不用修复所有的数据,只需要从有问题的步骤开始修复。
屏蔽原始数据的异常。
屏蔽业务的影响,不必改一次业务就需要重新接入数据
9 流批一体
批处理与流计算是大数据处理中的两种模式,分别应用在离线处理和实时处理方面,批处理优势是计算简单、吞吐量大且方便维护,不足的是时效性差,流处理的优势是处理及时但吞吐性不高,易出错。然而在实践中,两种模式常混合一起使用,由此诞生了2种常用的大数据架构:Lambda 架构和Kappa架构。
9.1 Lambda 架构
Lambda 架构是目前影响最深刻的大数据处理架构,它的核心思想是将不可变的数据以追加的方式并行写到批和流处理系统内,随后将相同的计算逻辑分别在流和批系统中实现,并且在查询阶段合并流和批的计算视图并展示给用户。Lambda的提出者 Nathan Marz 还假定了批处理相对简单不易出现错误,而流处理相对不太可靠,因此流处理器可以使用近似算法,快速产生对视图的近似更新,而批处理系统会采用较慢的精确算法,产生相同视图的校正版本。
Lambda架构典型数据流程是(http://lambda-architecture.net/):
所有的数据需要分别写入批处理层和流处理层;
批处理层两个职责:(i)管理 master dataset (存储不可变、追加写的全量数据),(ii)预计算batch view;
服务层对 batch view 建立索引,以支持低延迟、ad-hoc 方式查询 view;
流计算层作为速度层,对实时数据计算近似的 real-time view,作为高延迟batch view 的补偿快速视图;
所有的查询需要合并 batch view 和 real-time view;
Lambda 架构设计推广了在不可变的事件流上生成视图,并且可以在必要时重新处理事件的原则,该原则保证了系统随需求演进时,始终可以创建相应的新视图出来,切实可行地满足了不断变化的历史数据和实时数据分析需求。但
Lambda 架构非常复杂,在数据写入、存储、对接计算组件以及展示层都有复杂的子课题需要优化:
1. 写入层上,Lambda 没有对数据写入进行抽象,而是将双写流批系统的一致性问题反推给了写入数据的上层应用;
2. 存储上,以 HDFS 为代表的master dataset 不支持数据更新,持续更新的数据源只能以定期拷贝全量 snapshot 到 HDFS 的方式保持数据更新,数据延迟和成本比较大;
3. 计算逻辑需要分别在流批框架中实现和运行,而在类似 Storm 的流计算框架和Hadoop MR 的批处理框架做 job 开发、调试、问题调查都比较复杂;
4. 结果视图需要支持低延迟的查询分析,通常还需要将数据派生到列存分析系统,并保证成本可控。
9.2 Kappa架构
针对 Lambda 架构的问题3,计算逻辑需要分别在流批框架中实现和运行的问题,不少计算引擎已经开始往流批统一的方向去发展,例如 Spark 和 Flink,从而简化lambda 架构中的计算部分。实现流批统一通常需要支持:
1.以相同的处理引擎来处理实时事件和历史回放事件;
2.支持 exactly once 语义,保证有无故障情况下计算结果完全相同;
3.支持以事件发生时间而不是处理时间进行窗口化。
Kappa 架构由 Jay Kreps 提出,不同于 Lambda 同时计算流计算和批计算并合并视图,Kappa 只会通过流计算一条的数据链路计算并产生视图。Kappa 同样采用了重新处理事件的原则,对于历史数据分析类的需求,Kappa 要求数据的长期存储能够以有序 log 流的方式重新流入流计算引擎,重新产生历史数据的视图。
Kappa 方案通过精简链路解决了1数据写入和3计算逻辑复杂的问题,但它依然没有解决存储和查询展示的问题,特别是在存储上,使用类似 kafka 的消息队列存储长期日志数据,数据无法压缩,存储成本很大,绕过方案是使用支持数据分层存储的消息系统(如 Pulsar,支持将历史消息存储到云上存储系统),但是分层存储的历史日志数据仅能用于 Kappa backfill 作业,数据的利用率依然很低。
Kappa 不是 Lambda 的替代架构,而是其简化版本,Kappa 放弃了对批处理的支持,更擅长业务本身为 append-only 数据写入场景的分析需求,例如各种时序数据场景,天然存在时间窗口的概念,流式计算直接满足其实时计算和历史补偿任务需求;
Lambda 直接支持批处理,因此更适合对历史数据有很多 ad hoc 查询的需求的场景,比如数据分析师需要按任意条件组合对历史数据进行探索性的分析,并且有一定的实时性需求,期望尽快得到分析结果,批处理可以更直接高效地满足这些需求。
解决上述两种架构缺点的流批一提架构才是发展的大趋势,改进的着眼点涉及到数据初次流入系统和流入系统后的二次处理和查询。在数据初次流入目前已经有Flink这样的流批一体架构,从API层面支持对流处理和批处理的统一支持。在流入系统后的二次处理上,目前主要有以lakehouse为代表的的开源框架以及像Hologres这样的商业云产品。他们共同需要解决的问题都是提供统一存储、计算开放与在线查询能力,示意图架构如下:
10 资源隔离与云原生
资源涉及到CPU、内存、网络、磁盘等,资源管理则涉及到资源的统计、隔离、分配与回收等,资源管理是分布式系统中最基础也是最重要的功能之一,决定着系统的性能和稳定性等多方面表现,关于资源管理框架,不得不提YARN和Kubernetes。前者主要应用于大数据处理框架的资源管理和调度等,比如Hive、Spark、Flink都可以借住YARN的资源管理能力,运行其上。后者作为一个自动化平台,主要应用于容器的编排和管理等。这里着重介绍YARN。
Hadoop1.0 中,MapReduce 的 JobTracker 负责了太多的工作,包括资源调度,管理众多的 TaskTracker 等工作,为了解耦和减轻MapReduce的职责,独立出了YARN。YARN从整体上还是属于master/slave模型,包括ResourceManage和NodeManager。ResourceManager 拥有系统所有资源分配的决定权,负责集群中所有应用程序的资源分配,拥有集群资源主要、全局视图。因此为用户提供公平的,基于容量的,本地化资源调度。根据程序的需求,调度优先级以及可用资源情况,动态分配特定节点运行应用程序。它与每个节点上的NodeManager和每一个应用程序的ApplicationMaster协调工作。ResourceManager的主要职责在于调度,即在竞争的应用程序之间分配系统中的可用资源,并不关注每个应用程序的状态管理。
ResourceManager主要有两个组件:Scheduler和ApplicationManager:Scheduler是一个资源调度器,它主要负责协调集群中各个应用的资源分配,保障整个集群的运行效率。Scheduler的角色是一个纯调度器,它只负责调度Containers,不会关心应用程序监控及其运行状态等信息。同样,它也不能重启因应用失败或者硬件错误而运行失败的任务。在Hadoop的MapReduce框架中主要有三种Scheduler:FIFO Scheduler,Capacity Scheduler和Fair Scheduler。ApplicationManager主要负责接收job的提交请求,为应用分配第一个Container来运行ApplicationMaster,还有就是负责监控ApplicationMaster,在遇到失败时重启ApplicationMaster运行的Container。Container是Yarn框架的计算单元,是具体执行应用task(如map task、reduce task)的基本单位。 一个Container就是一组分配的系统资源,现阶段只包含两种系统资源(之后可能会增加磁盘、网络、GPU等资源),由NodeManager监控,Resourcemanager调度。
NodeManager是yarn节点的一个“工作进程”代理,管理hadoop集群中独立的计算节点,主要负责与ResourceManager通信,负责启动和管理应用程序的container的生命周期,监控它们的资源使用情况(cpu和内存),跟踪节点的监控状态,管理日志等。并报告给RM。NodeManager在启动时向ResourceManager注册,然后发送心跳包来等待ResourceManager的指令,主要目的是管理resourcemanager分配给它的应用程序container。NodeManager只负责管理自身的Container,它并不知道运行在它上面应用的信息。在运行期,通过NodeManager和ResourceManager协同工作,这些信息会不断被更新并保障整个集群发挥出最佳状态。主要职责:
1、接收ResourceManager的请求,分配Container给应用的某个任务
2、和ResourceManager交换信息以确保整个集群平稳运行。ResourceManager就是通过收集每个NodeManager的报告信息来追踪整个集群健康状态的,而NodeManager负责监控自身的健康状态。
3、管理每个Container的生命周期
4、管理每个节点上的日志
5、执行Yarn上面应用的一些额外的服务,比如MapReduce的shuffle过程
ApplicationMaster负责与scheduler协商合适的container,跟踪应用程序的状态,以及监控它们的进度,ApplicationMaster是协调集群中应用程序执行的进程。每个应用程序都有自己的ApplicationMaster,负责与ResourceManager协商资源(container)和NodeManager协同工作来执行和监控任务 。
当一个ApplicationMaster启动后,会周期性的向resourcemanager发送心跳报告来确认其健康和所需的资源情况,在建好的需求模型中,ApplicationMaster在发往resourcemanager中的心跳信息中封装偏好和限制,在随后的心跳中,ApplicationMaster会对收到集群中特定节点上绑定了一定的资源的container的租约,根据Resourcemanager发来的container,ApplicationMaster可以更新它的执行计划以适应资源不足或者过剩,container可以动态的分配和释放资源。YARN作业调度流程如下图所示:
1、客户端程序向ResourceManager提交应用并请求一个ApplicationMaster实例,ResourceManager在应答中给出一个applicationID以及有助于客户端请求资源的资源容量信息。
2、ResourceManager找到可以运行一个Container的NodeManager,并在这个Container中启动ApplicationMaster实例
Application Submission Context发出响应,其中包含有:ApplicationID,用户名,队列以及其他启动ApplicationMaster的信息,
Container Launch Context(CLC)也会发给ResourceManager,CLC提供了资源的需求,作业文件,安全令牌以及在节点启动ApplicationMaster所需要的其他信息。
当ResourceManager接收到客户端提交的上下文,就会给ApplicationMaster调度一个可用的container(通常称为container0)。然后ResourceManager就会联系NodeManager启动ApplicationMaster,并建立ApplicationMaster的RPC端口和用于跟踪的URL,用来监控应用程序的状态。
3、ApplicationMaster向ResourceManager进行注册,注册之后客户端就可以查询ResourceManager获得自己ApplicationMaster的详细信息,以后就可以和自己的ApplicationMaster直接交互了。在注册响应中,ResourceManager会发送关于集群最大和最小容量信息,
4、在平常的操作过程中,ApplicationMaster根据resource-request协议向ResourceManager发送resource-request请求,ResourceManager会根据调度策略尽可能最优的为ApplicationMaster分配container资源,作为资源请求的应答发个ApplicationMaster
5、当Container被成功分配之后,ApplicationMaster通过向NodeManager发送container-launch-specification信息来启动Container, container-launch-specification信息包含了能够让Container和ApplicationMaster交流所需要的资料,一旦container启动成功之后,ApplicationMaster就可以检查他们的状态,Resourcemanager不在参与程序的执行,只处理调度和监控其他资源,Resourcemanager可以命令NodeManager杀死container,
6、应用程序的代码在启动的Container中运行,并把运行的进度、状态等信息通过application-specific协议发送给ApplicationMaster,随着作业的执行,ApplicationMaster将心跳和进度信息发给ResourceManager,在这些心跳信息中,ApplicationMaster还可以请求和释放一些container。
7、在应用程序运行期间,提交应用的客户端主动和ApplicationMaster交流获得应用的运行状态、进度更新等信息,交流的协议也是application-specific协议
8、一但应用程序执行完成并且所有相关工作也已经完成,ApplicationMaster向ResourceManager取消注册然后关闭,用到所有的Container也归还给系统,当container被杀死或者回收,Resourcemanager都会通知NodeManager聚合日志并清理container专用的文件。
Kubernetes作为云原生的核心技术,起着基础设施的作用,在云原生的大趋势下发展可谓如日中天。在生产环境中使用 Kubernetes 的主要优势在于它提供了在物理机或虚拟机集群上调度和运行容器的平台。更宽泛地说,它能帮你在生产环境中实现可以依赖的基于容器的基础设施。而且,由于 Kubernetes 本质上就是运维任务的自动化平台,Kubernetes不仅适用于离线大数据框架的资源调度,还适合于基于微服务的服务管理,深度体现了云原生的微服务、DevOps、持续交付和容器化4大因素,从而将系统的可靠性、伸缩性和可维护性提升到了新的高度。