第三部分的内容,主要讨论将多个不同数据系统(有着不同的数据模型,并针对不同的访问模式进行优化)集成为一个协调一致的应用架构时,会遇到的问题。从高层次看,存储和记录数据系统分为2大类:
️ 记录系统(System of record) :数据的权威版本(如果其他系统和记录系统之间存在任何差异,那么记录系统中的值是正确的)
️ 衍生数据系统(Derived data systems) :通常是另一个系统中的现有数据进行转换或处理的结果,如缓存、索引、物化视图等,推荐系统中,预测汇总数据通常衍生自用户日志
三种不同的数据处理系统:
1️⃣ 服务(在线系统)
2️⃣ 批处理系统(离线系统)
3️⃣ 流处理系统(准实时系统)
流处理和批处理最关键的区别是处理无界数据和有界数据
MPP数据库(大规模并行处理(MPP, massively parallel processing)专注于在一组机器上并行执行分析SQL查询,而MapReduce和分布式文件系统的组合则更像是一个可以运行任意程序的通用操作系统,批处理框架看起来越来越像MPP数据库了
基于Unix的awk,sed,grep,sort,uniq和xargs等工具的组合,可以轻松的帮助我们完成一些数据分析的工作,而且性能相当的好。而且,这些工具使用相同的接口,在Unix中,这种接口是一个file(准确的说是一个文件描述符)
文件是一个统一的接口,如果我们的程序的输入和输出都是文件,那么所有的程序缝合起来,像接力一样完成复杂的工作;统一的接口还包括URL和HTTP(我们可以在网站和网站之间无缝跳转)。这和函数式编程的理念非常类似
Unix工具很强大,但是其局限性就是只能在一台机器上运行,所以Hadoop这样的工具应运而生
Unix和MapReduce比对
MR | 除了生成输出没有副作用 | 简单粗暴却有效 | 分布式 | 分布式文件系统上读写文件 | 使用无共享架构 | 通过工作流(workflow)将多个MR作业连接在一起,文件 |
---|---|---|---|---|---|---|
Unix | 除了生成输出没有副作用 | 简单粗暴却有效 | 单机 | 使用stdin 和stdout 作为输入输出 |
共享架构 | 管道符,内存缓存区1 |
MapReduce是一个编程框架,可以使用它编写代码处理HDFS等分布式文件系统中的大型数据集,并且遵循移动计算大于移动数据的原则。MapReduce的数据处理过程如下:
1️⃣ 读取一组输入文件,并将其分解成记录(records)
2️⃣ 调用Mapper函数,从每条输入记录中提取一对键值;map的任务数由输入文件块的数量决定
3️⃣ 按键排序所有的键值对
4️⃣ 调用Reducer函数遍历排序后的键值对,相同的key,将会在reducer中相邻;reduce的任务数量是可配置的
其中第2️⃣4️⃣步是自定义数据处理代码的地方,第3️⃣步Mapper的输出始终在送往Reducer之前进行排序,无须编写。下图是三个Mapper和三个Reducer的MR任务:
Mapper中的数据去往Reducer的过程可以看做是Mapper将消息发送给Reducer,每当Mapper发出一个键值对,这个键的作用就好像是去往到目标地址(IP地址)
如下图,左侧是事件日志,右侧是用户数据库,任务需要将用户活动和用户档案相关联:
整个连接的MapReduce过程如下:
MapReduce框架通过键对Mapper输出进行分区,然后对键值对进行排序,使得具有相同ID的所有活动事件和用户记录在Reducer输入中彼此相邻。 Map-Reduce作业可以进一步让这些记录排序,使Reducer总能先看到来自用户数据库的记录,紧接着是按时间戳顺序排序的活动事件(二次排序/secondary sort)
然后Reducer可以执行实际的连接逻辑:每个用户ID都会被调用一次Reducer函数,且因为二次排序,第一个值应该是来自用户数据库的出生日期记录。 Reducer将出生日期存储在局部变量中,然后使用相同的用户ID遍历活动事件,输出已观看网址和观看者年龄的结果对。随后的Map-Reduce作业可以计算每个URL的查看者年龄分布,并按年龄段进行聚集
因为Mapper的输出是按键排序的,然后Reducer将来自连接两侧的有序记录列表合并在一起,所以这个算法被称为排序合并连接(sort-merge join)
MapReduce实现这分组操作的方法是设置Mapper,使得Mapper生成的键值对使用所需的分组键
热键(hot pot)和倾斜连接(skewed join) : 热键是指记录中某个键记录数显著高于其他的键,热键关联是会产生倾斜关联(1个Reducer会处理比其他Reducer更多的记录);一般处理倾斜连接方式是分2次MR
在Reducer端连接中,排序,复制至Reducer,以及合并Reducer输入,所有这些操作可能开销巨大,如果数据具备某些特性,或许可以使用一些性能更优的连接方式,如:
1️⃣广播散列连接:在小表足够小的情况下,将小表读取到内存散列表中,然后Mapper扫描大表在散列表中查找每个事件
2️⃣ 分区散列连接:本质是GRACE Hash Join,在Hive中叫做:Map 端桶连接
3️⃣ Map端合并连接:本质是 sort-merge-join
批处理的常见用途是:构建机器学习系统(分类器/推荐系统等),建立搜索索引(google最初使用MR就是为其搜索索引建立索引),批处理的输出哲学和Unix一致,除了产生输出不会产生任何副作用,即容错能力高
Hadoop很像Unix的分布式版本,其中HDFS是分布式文件系统,MapReduce是Unix进程的变种实现,我们一直讨论的并行连接算法在MPP数据库中已有实现,区别在于MPP数据库专注于在一组机器上并行执行分析SQL,而MapReduce和分布式文件系统的组合更像是可以运行任意通用程序的操作系统
存储多样性 | 处理模型 | 故障处理 | |
---|---|---|---|
Hadoop | 字节序列 | MapReduce模型,SQL模型等 处理模型多样性3 |
针对故障频繁而设计4 |
分布式数据库 | 要求特定的模型(关系/文档) | SQL模型5 | 查询失效时,多数MPP会终止查询 |
:使用原始的MapReduce API
来实现复杂的处理工作实际上是非常困难,所以在MapReduce上有很多高级编程模型(Pig,Hive,Cascading,Crunch)被创造出来。️方面,MapReduce非常稳健;️方面,对于某些类型的处理而言,其他工具有时会快上几个数量级。流处理组件(storm、spark、flink)可以认为是解决"慢"这个问题而被发展出来的,物化中间状态也是一种加速方式
将数据发布到分布式文件系统中众所周知的位置能够带来松耦合,这样作业就不需要知道是谁在提供输入或谁在消费输出,一个作业的输出只能用作另一个作业的输入的情况下,分布式文件系统上的文件只是简单的中间状态(intermediate state):一种将数据从一个作业传递到下一个作业的方式。将这个中间状态写入文件的过程称为物化(materialization)[22][23]
link: https://en.wikipedia.org/wiki/Materialized_view + https://stackoverflow.com/questions/93539/what-is-the-difference-between-views-and-materialized-views-in-oracle + https://en.wikipedia.org/wiki/Materialized_view
:Unix管道将一个命令的输出与另一个命令的输入连接起来。管道并没有完全物化中间状态,而是只使用一个小的内存缓冲区,将输出增量地**流(stream)**向输入,与Unix管道相比,MapReduce完全物化中间状态的方法的不足之处在于:
1️⃣MapReduce作业只有在前驱作业(生成其输入)中的所有任务都完成时才能启动,而由Unix管道连接的进程会同时启动,输出一旦生成就会被消费
2️⃣ Mapper通常是多余的,如果Reducer和Mapper的输出有着相同的分区与排序方式,那么Reducer就可以直接串在一起,而不用与Mapper相互交织
3️⃣ 将中间状态存储在分布式文件系统中意味着这些文件被复制到多个节点
为了解决MapReduce的这些问题,几种用于分布式批处理的新执行引擎(Spark、Tez、Flink)被开发出来,它们的设计方式有很多区别,但有一个共同点:把整个工作流作为单个作业来处理,而不是把它分解为独立的子作业。由于它们将工作流显式建模为数据从几个处理阶段穿过,所以这些系统被称为数据流引擎(dataflow engines),像MapReduce一样,它们在一条线上通过反复调用用户定义的函数来一次处理一条记录,这些函数为算子(operators),数据流引擎提供了几种不同的选项来将一个算子的输出连接到另一个算子的输入:
与MapReduce模型相比,它有几个优点:
1️⃣排序等昂贵的工作只需要在实际需要的地方执行,而不是默认地在每个Map和Reduce阶段之间出现
2️⃣没有不必要的Map任务,因为Mapper所做的工作通常可以合并到前面的Reduce算子中(因为Mapper不会更改数据集的分区)
3️⃣由于工作流中的所有连接和数据依赖都是显式声明的,因此调度程序能够总览全局,知道哪里需要哪些数据,因而能够利用局部性进行优化。例如,它可以尝试将消费某些数据的任务放在与生成这些数据的任务相同的机器上,从而数据可以通过共享内存缓冲区传输,而不必通过网络复制
4️⃣通常,算子间的中间状态足以保存在内存中或写入本地磁盘,这比写入HDFS需要更少的I/O(必须将其复制到多台机器,并将每个副本写入磁盘)。 MapReduce已经对Mapper的输出做了这种优化,但数据流引擎将这种思想推广至所有的中间状态
5️⃣ 算子可以在输入就绪后立即开始执行;后续阶段无需等待前驱阶段整个完成后再开始
6️⃣ 与MapReduce(为每个任务启动一个新的JVM)相比,现有Java虚拟机(JVM)进程可以重用来运行新算子,从而减少启动开销
你可以使用数据流引擎执行与MapReduce工作流同样的计算,而且由于此处所述的优化,通常执行速度要明显快得多。相同的处理逻辑,可以通过修改配置切换底层计算引擎,简单地从MapReduce切换到Tez或Spark6
本章主要讲述了Unix的管道思想,MapReduce与其接口HDFS,最后是数据流引擎构建自己的管道式的数据传输机制。并且还讨论了分布式批处理框架要解决的2个主要问题:
️ 分区:这一过程的目的是把所有的相关数据(例如带有相同键的所有记录)都放在同一个地方
️容错:MapReduce经常写入磁盘,这使得从单个失败的任务恢复很轻松,无需重新启动整个作业,但在无故障的情况下减慢了执行速度。数据流引擎更多地将中间状态保存在内存中,更少地物化中间状态,这意味着如果节点发生故障,则需要重算更多的数据。确定性算子减少了需要重算的数据量
以上是《设计数据密集型应用》读书笔记的第5部分,欢迎吐槽,欢迎关注公众号:stackoverflow
多个MR任务连接的方式是将后一个MR任务的输入配置为前一个MR任务的输出;而Unix命令管道是直接将一个进程的输出作为另外一个进程的输入,仅用一个很小的内存缓冲区 ↩︎
关于上述三种连接方式请参阅:https://mp.weixin.qq.com/s/lulNpgxillQ0s5fb_rgvdw ↩︎
并非所有数据类型的处理都可以合理的用SQL表达(推进系统、特征工程等),所以编写代码是必须的,而MR能使得工程师可以轻松的在大型数据集上执行自己的代码,甚至还可以基于MapReduce+HDFS 建立SQL查询执行引擎,比如Hive就是这么做的;除了SQL和MapReduce之外,由于Hadoop平台的开放性,还可以构建更多的模型。由于不需要将数据导入到专门的系统进行不同类型的处理,采用新的处理模型也更容易。如MPP风格的分析型数据库impala,随机访问风格的OLTP数据库HBase(LSM) ↩︎
落盘一方面是容错,一方面是假设数据集太大不能适应内存。并且支持支持资源的过度使用 ↩︎
MPP 是单体的紧密集成的软件,负责磁盘上的存储布局,查询计划,调度和执行,这些组件针对数据库的特定需求做了优化,因此可以对特定查询有很好的性能,但只支持SQL模型 ↩︎
Tez是一个相当薄的库,它依赖于YARN shuffle服务来实现节点间数据的实际复制,而Spark和Flink则是包含了独立网络通信层,调度器,及用户向API的大型框架 ↩︎