hadoop

HDFS,Yarn,MapReduce是怎么工作的?

作者:小怪兽
链接:https://www.zhihu.com/question/27974418/answer/1862026844

1

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 时刻的数据才刚到齐。但回过头来说,在大多数场景下,毕竟乱序或迟到数据算是占比很小了。