如何用形象的比喻描述大数据的技术生态?Hadoop、Hive、Spark 之间是什么关系?

对于我们这些文科,商科生来说。我们刚刚搞懂服务器,数据库,C++,Java等基础语言是个什么东西的时候,大数据时代来了,科技蜀黍又玩起Hadoop,H…
关注者
8,107
被浏览
1,143,732

95 个回答

2023-11-22 更新说明,由于工作繁忙几乎没时间更新,大家有遇到自己业务场景的问题可以评论,本人才疏学浅,但如果有解决过类似问题我会回答。


Hadoop只是一套工具的总称,它包含三部分:HDFS,Yarn,MapReduce,功能分别是分布式文件存储、资源调度和计算。

按理来说,这就足够了,就可以完成大数据分析了。

但第一个问题就是麻烦。这一套相当于用Yarn调度资源,读取HDFS文件内容进行MR计算。要写Java代码,但做数据的最好的工具是什么?SQL!所以Hive相当于这一套标准流程的SQL化。

Hive可以简单理解为,Hadoop之上添加了自己的SQL解析和优化器,写一段SQL,解析为Java代码,然后去执行MR,底层数据还是在HDFS上。

这看起来挺完美,但问题是程序员发现好慢啊。原因是MR,它需要频繁写读文件。这时基于内存的Spark出现了,Spark是替代MR的,它会为SQL生成有向无环图,加上各种算子和宽窄依赖的优化,使得计算速度达到了新的高度。

按理说这就完美解决了呀。

但是,我们回头想想,这些数据怎么来的呢?我们是不是到目前为止都是在处理静态的数据呢?像比如线上支付校验这种需要实时返回结果的总不能等着Spark批量算吧。

解决问题之前,我们回头再想想,数据怎么来的。一般数据包含两种:业务数据和日志数据。

业务数据就是数据库中的结构性的数据,规规整整。业务数据怎么到Hive呢?开源上一般通过Sqoop进行导入,比如一张表,数据少每天我把表全部导入一遍,这叫全量同步;数据特别大,就只同步每天变化和新增的,这是增量同步。

但这种同步比较滞后,都是在夜深人静集群的计算资源比较空闲的时候做的,对应的也是离线分析。

实时的数据产生了该怎么拿到呢?

2

实时怎么理解?来一批处理一批,再细一点儿,来一条,处理一条。

比如,你买一件东西,平台数据库中会多一条订单数据,app会产生行为日志数据。订单数据插入数据库时一般会有binlog,即记录插入、更新或删除的数据,我们只要能实时拿到这一条binlog,就相当于拿到了实时数据。

binlog怎么拿呢?这就要说道数据库的主从备份机制,一般本身就是拿主库的binlog同步到备份库,刚好有一个叫canal的工具可以把自己伪装成备份库,来拉取主库的binlog,再解析、包装最后抛出,就相当于实时拿到数据了!

canal拿到了binlog就能直接处理了吗?可以,但有件事儿大家想一想。马上五一了,加入一下子超级多人下单消费,canal抛出的消息我们下游一下子消费不完咋办呢?比如快递员每天都只给你派送一件快递,你拿到之后钱货两清。然后突然一天快递员给你送一千件到你楼下,你下楼一件一件搬,快递员还得等你搬完才能回去,这得等到啥时候。聪明的你马上想到了,放快递柜呀,你有时间慢慢搬不就行了,也不占用快递员的时间了。

这就是消息队列,Kafka 就是起这样的作用:异步、解耦、消峰。canal的数据一般会抛到kafka或RocketMQ,可以保存一段时间。然后下游程序再去实时拉取消息来计算。

3

说了这么多下游,下游到底由谁来消费计算这些实时数据呢?还记得Spark吗,没错它又来了,Spark streaming就是处理实时流数据的好手。

Spark 是一整套组件的统称,比如你可以用 Java 写 Spark 任务,用 Spark SQL 去写 SQL,可以用 Spark MLib 完成机器学习的模型训练等等,Spark Streaming 就是用来微批地处理流式数据的。

具体而言,离线数据我们是等半夜数据都抽到 Hive 中再计算,而 Spark Streaming 则是实时数据来一小批,它就处理一小批。所以本质上讲,Spark Streaming 还是批处理,只不过是每一批数据很少,并且处理很及时,从而达到实时计算的目的。

Spark 本身的流行使得 Spark Streaming 也一直大范围使用。

这一套有什么逻辑缺陷吗?

我们可以想一想,实时数据和离线数据最大的差异,是时效性。离线数据像湖水,该多少就多少,就在那里;实时数据像水流,绵绵不绝。时间,便是非常重要的一个特质。当一条数据来的时候,我们需要知道这条数据是什么时候产生的,这便是业务时间。但我们拿到这条数据时往往是业务时间之后的一小会,这边是处理时间。真正世界里的实时数据肯定不是像 Spark Streaming 那样一批一批来的,而是一个一个的事件。对此,Flink 帮助我们解决了这些问题。

4

无论是业务数据还是日志数据,往往都有相应的时间标志字段,代表着这条消息的业务时间。你可以让 Flink 选择这个时间,这样,Flink 就知道当前处理到哪个时间点了。

Flink 不同于 Spark Streaming 的微批次处理,它是一条一条数据处理的。这样的数据一般是先来后到的,但难免会有些数据沿途受阻晚来了几秒钟,这就会导致两个问题:数据延迟和乱序数据。这也是做实时数据的非常关注的问题。

如何防止数据延迟?如果是上游数据迟了,就加大上游资源;如果是数据突然激增,导致 Flink 处理不过来导致任务出现延迟,就加大 Flink 的资源,比如并发。

数据乱序呢?

同样的,我们一般也通过上游和 Flink 本身来分别保证。

我们上面提到了消息的快递柜 Kafka,Kafka 有分区的概念,就像是不同的通道,一条消息来了后,可以走 A,也可以走 B,也可以走 C。那么问题来了,现在面试官问你,业务数据抛入 Kafka,如何保证消息的顺序性呢?

(5月4日 更)

顺序性一般有两方面需要保证。我们举一个小小的例子,一个用户下单的场景,有两个基本共识:

  1. 同一个用户的订单状态会先后变化;
  2. 不同用户的不同订单也有先后之分。

所以我们解决数据的顺序性一般也是从这两方面考虑。如果你还记得大学高数里的多元函数求偏导,对于 x 和 y 两个变量,求 x 的偏导会假设 y 为常量,反之同理。我们考虑这个问题也一样,如果不能同时兼顾这两方面,那就一个一个去优化吧!这种思想也称为贪婪算法,在很多地方都有应用,这里暂时说到这里。

回到问题,那么如何保证同一用户的订单顺序呢?很简单,前面我们提到的链路是,数据库中插入或更新数据时,会实时产生该条数据的 binlog,canal 获取、解析、包装这条 binlog 并抛入 Kafka。

而 Kafka 由于有分区的存在,很可能同一个订单的消息会被发送到不同的分区中,这样的话,如果下游的 Flink 任务消费不同分区速率不同,就可能导致先到的数据反而被后消费,产生顺序误差。解决的办法即保证同一订单的消息进入 Kafka 的同一分区即可。

Kafka 的每一条消息都会有 messageKey 和 message 两个结构,如果没有直接给消息指定分区,那么 messageKey 决定了消息进入哪个分区,在 canal 中,我们便可以设定消息如何进入 Kafka。数据库中的业务数据,会存在一张张的表中,表一般都会有主键,来唯一标识一条数据,我们一般也就是通过设定 canal 选择 binlog 所在表的主键来决定其进入 Kafka 的分区。这样,就基本解决了第一个问题。

(5月9日 更)

但这只保证了同一订单数据的顺序性,并未保证不同订单之间的顺序性。聪明的你可能已经想到,如果 Kafka 只设定一个分区那不就保证了吗?但这其实算是本末倒置,Kafka 本身相当于快递柜,多个分区相当于多个柜子,能存储更多的数据,提高并发,如果为了顺序性而牺牲并发量,那就得不偿失了,而且一般本身数据的乱序无论是在概率和重要性方面都不如并发重要的。就比如我要统计每小时的订单数,即使数据乱序了,只要在窗口区间内计算结果也不怎么受影响。

但这并不是说我们就不考虑数据在全局的顺序性了。

我们如何去认识乱序或延迟数据呢?

既然这种情况是偶发性的,那么一般可以这么做,在实时的流数据中,如果想要拿到 T 时刻的数据,只要等一小会儿比如 1s,就能保证在 T+1s 的时刻拿到 T 时刻的所有数据。

上面这句话其实理解起来也很简单,比如幼儿园老师组织小朋友们春游,约定了早上 8:00 集合发车,即 8:00 触发一个事件。但总有那么几个调皮捣蛋的学生会迟到几分钟,于是老师说好的 8 点发车实际上是8:05,大家觉得也没啥问题,回家就跟家长说,我们今天 8:00 发车春游啦。

在 Flink 中,这种机制就叫做 watermark。

上面我们说过,每一条数据一般都会自带一个时间字段,来标志这条数据的业务时间,即什么时候发生的。然后 Flink 提取这个时间字段,就知道了目前 Flink 任务进行到几点了。

那么既然要考虑乱序或迟到数据,我们一般也会让 Flink 当前的时间稍微迟几秒钟。比如我们认为大部分情况下乱序或迟到的数据都在 1s 以内,那么来一条数据,比如这条数据自带的时间是 08:00:01,那我们就认为 08:00:00 时刻的数据才刚到齐。但回过头来说,在大多数场景下,毕竟乱序或迟到数据算是占比很小了。

5

是不是看到这里有点抽象了?下一节我们聊聊 SQL 吧。

7大数据本身是个很宽泛的概念,Hadoop生态圈(或者泛生态圈)基本上都是为了处理超过单机尺度的数据处理而诞生的。你可以把它比作一个厨房所以需要的各种工具。锅碗瓢盆,各有各的用处,互相之间又有重合。你可以用汤锅直接当碗吃饭喝汤,你可以用小刀或者刨子去皮。但是每个工具有自己的特性,虽然奇怪的组合也能工作,但是未必是最佳选择。

大数据,首先你要能存的下大数据。
传统的文件系统是单机的,不能横跨不同的机器。HDFS(Hadoop Distributed FileSystem)的设计本质上是为了大量的数据能横跨成百上千台机器,但是你看到的是一个文件系统而不是很多文件系统。比如你说我要获取/hdfs/tmp/file1的数据,你引用的是一个文件路径,但是实际的数据存放在很多不同的机器上。你作为用户,不需要知道这些,就好比在单机上你不关心文件分散在什么磁道什么扇区一样。HDFS为你管理这些数据。

存的下数据之后,你就开始考虑怎么处理数据。虽然HDFS可以为你整体管理不同机器上的数据,但是这些数据太大了。一台机器读取成T上P的数据(很大的数据哦,比如整个东京热有史以来所有高清电影的大小甚至更大),一台机器慢慢跑也许需要好几天甚至好几周。对于很多公司来说,单机处理是不可忍受的,比如微博要更新24小时热博,它必须在24小时之内跑完这些处理。那么我如果要用很多台机器处理,我就面临了如何分配工作,如果一台机器挂了如何重新启动相应的任务,机器之间如何互相通信交换数据以完成复杂的计算等等。这就是MapReduce / Tez / Spark的功能。MapReduce是第一代计算引擎,Tez和Spark是第二代。MapReduce的设计,采用了很简化的计算模型,只有Map和Reduce两个计算过程(中间用Shuffle串联),用这个模型,已经可以处理大数据领域很大一部分问题了。
那什么是Map什么是Reduce?
考虑如果你要统计一个巨大的文本文件存储在类似HDFS上,你想要知道这个文本里各个词的出现频率。你启动了一个MapReduce程序。Map阶段,几百台机器同时读取这个文件的各个部分,分别把各自读到的部分分别统计出词频,产生类似
(hello, 12100次),(world,15214次)等等这样的Pair(我这里把Map和Combine放在一起说以便简化);这几百台机器各自都产生了如上的集合,然后又有几百台机器启动Reduce处理。Reducer机器A将从Mapper机器收到所有以A开头的统计结果,机器B将收到B开头的词汇统计结果(当然实际上不会真的以字母开头做依据,而是用函数产生Hash值以避免数据串化。因为类似X开头的词肯定比其他要少得多,而你不希望数据处理各个机器的工作量相差悬殊)。然后这些Reducer将再次汇总,(hello,12100)+(hello,12311)+(hello,345881)= (hello,370292)。每个Reducer都如上处理,你就得到了整个文件的词频结果。
这看似是个很简单的模型,但很多算法都可以用这个模型描述了。
Map+Reduce的简单模型很黄很暴力,虽然好用,但是很笨重。第二代的Tez和Spark除了内存Cache之类的新feature,本质上来说,是让Map/Reduce模型更通用,让Map和Reduce之间的界限更模糊,数据交换更灵活,更少的磁盘读写,以便更方便地描述复杂算法,取得更高的吞吐量。

有了MapReduce,Tez和Spark之后,程序员发现,MapReduce的程序写起来真麻烦。他们希望简化这个过程。这就好比你有了汇编语言,虽然你几乎什么都能干了,但是你还是觉得繁琐。你希望有个更高层更抽象的语言层来描述算法和数据处理流程。于是就有了Pig和Hive。Pig是接近脚本方式去描述MapReduce,Hive则用的是SQL。它们把脚本和SQL语言翻译成MapReduce程序,丢给计算引擎去计算,而你就从繁琐的MapReduce程序中解脱出来,用更简单更直观的语言去写程序了。

有了Hive之后,人们发现SQL对比Java有巨大的优势。一个是它太容易写了。刚才词频的东西,用SQL描述就只有一两行,MapReduce写起来大约要几十上百行。而更重要的是,非计算机背景的用户终于感受到了爱:我也会写SQL!于是数据分析人员终于从乞求工程师帮忙的窘境解脱出来,工程师也从写奇怪的一次性的处理程序中解脱出来。大家都开心了。Hive逐渐成长成了大数据仓库的核心组件。甚至很多公司的流水线作业集完全是用SQL描述,因为易写易改,一看就懂,容易维护。

自从数据分析人员开始用Hive分析数据之后,它们发现,Hive在MapReduce上跑,真鸡巴慢!流水线作业集也许没啥关系,比如24小时更新的推荐,反正24小时内跑完就算了。但是数据分析,人们总是希望能跑更快一些。比如我希望看过去一个小时内多少人在充气娃娃页面驻足,分别停留了多久,对于一个巨型网站海量数据下,这个处理过程也许要花几十分钟甚至很多小时。而这个分析也许只是你万里长征的第一步,你还要看多少人浏览了跳蛋多少人看了拉赫曼尼诺夫的CD,以便跟老板汇报,我们的用户是猥琐男闷骚女更多还是文艺青年/少女更多。你无法忍受等待的折磨,只能跟帅帅的工程师蝈蝈说,快,快,再快一点!
于是Impala,Presto,Drill诞生了(当然还有无数非著名的交互SQL引擎,就不一一列举了)。三个系统的核心理念是,MapReduce引擎太慢,因为它太通用,太强壮,太保守,我们SQL需要更轻量,更激进地获取资源,更专门地对SQL做优化,而且不需要那么多容错性保证(因为系统出错了大不了重新启动任务,如果整个处理时间更短的话,比如几分钟之内)。这些系统让用户更快速地处理SQL任务,牺牲了通用性稳定性等特性。如果说MapReduce是大砍刀,砍啥都不怕,那上面三个就是剔骨刀,灵巧锋利,但是不能搞太大太硬的东西。

这些系统,说实话,一直没有达到人们期望的流行度。因为这时候又两个异类被造出来了。他们是Hive on Tez / Spark和SparkSQL。它们的设计理念是,MapReduce慢,但是如果我用新一代通用计算引擎Tez或者Spark来跑SQL,那我就能跑的更快。而且用户不需要维护两套系统。这就好比如果你厨房小,人又懒,对吃的精细程度要求有限,那你可以买个电饭煲,能蒸能煲能烧,省了好多厨具。

上面的介绍,基本就是一个数据仓库的构架了。底层HDFS,上面跑MapReduce/Tez/Spark,在上面跑Hive,Pig。或者HDFS上直接跑Impala,Drill,Presto。这解决了中低速数据处理的要求。

那如果我要更高速的处理呢?
如果我是一个类似微博的公司,我希望显示不是24小时热博,我想看一个不断变化的热播榜,更新延迟在一分钟之内,上面的手段都将无法胜任。于是又一种计算模型被开发出来,这就是Streaming(流)计算。Storm是最流行的流计算平台。流计算的思路是,如果要达到更实时的更新,我何不在数据流进来的时候就处理了?比如还是词频统计的例子,我的数据流是一个一个的词,我就让他们一边流过我就一边开始统计了。流计算很牛逼,基本无延迟,但是它的短处是,不灵活,你想要统计的东西必须预先知道,毕竟数据流过就没了,你没算的东西就无法补算了。因此它是个很好的东西,但是无法替代上面数据仓库和批处理系统。

还有一个有些独立的模块是KV Store,比如Cassandra,HBase,MongoDB以及很多很多很多很多其他的(多到无法想象)。所以KV Store就是说,我有一堆键值,我能很快速滴获取与这个Key绑定的数据。比如我用身份证号,能取到你的身份数据。这个动作用MapReduce也能完成,但是很可能要扫描整个数据集。而KV Store专用来处理这个操作,所有存和取都专门为此优化了。从几个P的数据中查找一个身份证号,也许只要零点几秒。这让大数据公司的一些专门操作被大大优化了。比如我网页上有个根据订单号查找订单内容的页面,而整个网站的订单数量无法单机数据库存储,我就会考虑用KV Store来存。KV Store的理念是,基本无法处理复杂的计算,大多没法JOIN,也许没法聚合,没有强一致性保证(不同数据分布在不同机器上,你每次读取也许会读到不同的结果,也无法处理类似银行转账那样的强一致性要求的操作)。但是丫就是快。极快。
每个不同的KV Store设计都有不同取舍,有些更快,有些容量更高,有些可以支持更复杂的操作。必有一款适合你。

除此之外,还有一些更特制的系统/组件,比如Mahout是分布式机器学习库,Protobuf是数据交换的编码和库,ZooKeeper是高一致性的分布存取协同系统,等等。

有了这么多乱七八糟的工具,都在同一个集群上运转,大家需要互相尊重有序工作。所以另外一个重要组件是,调度系统。现在最流行的是Yarn。你可以把他看作中央管理,好比你妈在厨房监工,哎,你妹妹切菜切完了,你可以把刀拿去杀鸡了。只要大家都服从你妈分配,那大家都能愉快滴烧菜。

你可以认为,大数据生态圈就是一个厨房工具生态圈。为了做不同的菜,中国菜,日本菜,法国菜,你需要各种不同的工具。而且客人的需求正在复杂化,你的厨具不断被发明,也没有一个万用的厨具可以处理所有情况,因此它会变的越来越复杂。