00:00
了解了ampi当中的基本换算子,接下来我们再来看另外一类算子,那就是所谓的聚合算子。它跟之前我们讲的基本转换算子有什么区别呢?那回忆一下的话,就会发现,所谓的基本转换算子,它其实本质上都是基于当前数据去进行了一个转换。也就是说,不管是一对一的map,还是有可能一对零进行了筛选过滤的filter,还是后边我们讲到可以非常灵活的进行一对一对一、一对多进行设定进行筛选的flat,不管是怎么样,他们基于的数据输入的数据都只有当前一条数据。也就是说其他的数据。不会影响到当前这个数据进行转换计算的结果。在实际的应用场景里边呢,这种转换计算比较常见,但是也也比较简单,我们真实的应用其实往往不会只做这种基于当前数据的转换。
01:07
而是需要考虑,比如说我们当前的所有数据加起来一共是多少呢?那或者更加简单的,就像我们之前word count那样,统计一下当前每一个符合某种条件的每一个词,它出现的个数到底是多少呢?在这种过程当中,我们就会发现,只来一条数据,我们只针对当前数据的话,很显然是得不到当前的个数或者当前的总和的,它是依赖于之前的所有数据。那这种操作我们需要把之前所有的大量数据。它的某一个特征,它的某一个属性字段进行统计整合,这种操作就叫做聚合。啊,所以这种聚合算子、聚合操作在实际应用过程当中其实是更加普遍,更加有用的,因为如果我们只是看某一条数据的话,这跟大数据是没有关系的,我们真正做大数据处理,就是希望从大量海量的数据当中提取出、整合出最为关键、更为有用的信息。
02:16
那对应着我们之前所熟悉的大数据架构的话。前面的基本转换算子我们可以认为都是ma操作,那后边的聚合算子当然对应的就是reduce操作。具体介绍聚合算子之前,我们先来说一个比较特殊的接口。一个方法调用,那就是所谓的key办,往往可以把它翻译成按键分区,这是做什么任务?这是要做什么事情呢?呃,其实我们知道对于大数据处理而言,海量数据进行聚合,如果说我们是把所有的数据都放在一起进行统计的话,那相当于我们就是一个。的单线程任务嘛,哎,那这样的话,我们处理的效率其实是非常低的,如果说我们希望做分布式处理进行。
03:09
能计算的话,那很明显我们需要对于数据首先进行分区操作,然后接下来再去做聚合,这样效率会更高。所以在flink的data stream API里面,基于data stream,它是没有办法直接进行聚合的,我们可以感兴趣的话可以在源码里边去考察一下我们知道得到的dataream source。它本质上就是一个data stream,那在data stream里边,我们直接查看当前类可以调用的所有方法,就会发现有没有直接的,或者我们想要做一个aggregate,或者呃,最简单的我们可能想到可以去sum,诶,完全没有对应的这些聚合操作。那怎么样才能够进行聚合操作呢?必须要先分区,这里边我们的分区方式就是K。
04:05
K8,那我们可以看到里边就需要先传入对应的K,指定当前的K到底是什么,然后按照。不同的K把当前一条数据流上的所有数据元素从逻辑上划分成不同的分区。注意,这里所说的分区是逻辑上进行的分区,它并不指代我们真正物理上进行并行处理、并行计算的不同分区。也就是说基于不同的T呢数据。有可能被划分到真正物理上的,不同的slot,不同的物理分区,但是呢,不同的K数据也有可能会划分到同一个里边来。诶,那主要这涉及到为什么会这样去定义呢?主要就是因为当前我们要分分的区分的这个K有可能会非常的多,特别是海量数据里边。
05:06
比方说像这个用户的访问数据,点击数据,那很有可能就是以user用户名作为一个分区的字段,一个P,那如果说拿用户名来进行分区的话,那很明显了,在一个正常的应用当中,拿用户名作为K,我们要做的分区应该是啊。有几万几十万甚至上百万上千万的,那对于这样的一种分组的话,我们对应的物理分区slot,如果这个并行度要设那么高,可能对于我们整个的系统架构,系统资源的耗费也会非常的大,最好情况当然是每一个就分到一个物理分区了,但是实际情况我们没有那么多物理分区啊,我们的并行度没有办法设那么大啊,这是受我们的硬件资源限制的。所以我们会发现,实际的硬件资源区分的并行度可能远远不如K那么多,那这个时候怎么办呢?
06:02
那我们就把K可以按照某种标准去进行一个分配。有一些K符合某种条件的K就划分到同一个分区里面去,那其他的一些呢,可能就分到了另外的一些分区去。那对于弗林而言,最基本的这个K操作,它的分区标准就是基于当前K的哈希值,基于它的哈希code去做,然后做了一个曲模运算,分配到对应不同的分区上去。所以接下来我们就会发现啊,对于这样一个KY操作,它本质上呢。是对于不同的数据确实做了分区操作,但是并不是一个K就一定对应一个分区。所以我们会发现对于不同T的数据而言,可能会分配到同一个分区,也有可能分配到不同的分区,比如说这里的这个例子,我们提取当前每一个数据它的颜色啊,这里数据块的颜色就是当前我们的K对应的字段,那所以按照不同颜色我们进行了求它的哈希值,然后对于我们整个所有的这个分区数量。
07:17
做了一个取模运算,分配到了不同分区,那最后的效果呢?可以看到有可能浅色的数据。当前他都分配到了上面的一个分区,但是也有可能呢。比较深的颜色的数据以及很深的颜色的数据不同K他们都划分到了同一个分区。那对应我们从结果上来看的话,那就是同一个物理分区,同一个slot上要处理的数据有可能是同一个K的,也有可能有多个K。至少这里面我们能确定的是相同T的数据。他一定只会被分配到同一个分区。
08:02
而不同T的数据呢,有可能分到同一个分区,也有可能是不同的分区。这就是K败的含义。啊,那它的具体使用我们在源码当中也看到了,它有不同的传参方式可以进行重载啊,那这里面最常见的方法就是直接传入一个key select,那这里的key select呢?很简单,它也是一个具有单一抽象方法的接口。它具有两个泛型,一个是in,当然就是当前传入的数据类型了,那另外还有一个是T,就是我们指定提取出的那个键值key的类型啊,那当前它必须要实现的抽象方法就叫做get key。它的参数,传入的参数就是当前的数据,而对应的返回值就是提取出的key,这就是所谓的KBY,我们想要去传入的东西啊,那当然了,这种既然是这样的一个单一抽象方法的接口,我们可以直接去,你有一个类实现这个接口也可以传一个匿名类,当然了也可以使用拉姆达表达式,那对于这个select而言,我们知道。
09:17
只要是筛选出的key是简单类型的话,那那就不需要再单独的去指定returns,去指定当前的返回类型嘛,那肯定直接使用拉姆达表达式是最方便的。那有了这样的一个。KY操作之后,我们会发现它的返回值现在就不像之前的那些map Fla map基本转换算子返outp。K street,那k string的话,我们可以认为是指定了key的一个数据流,所以有时候把这个翻译会翻译成是一个叫按键分区流,或者叫键控流。
10:04
它本质上呢,其实就是针对data stream增加了一个K的定义,按照K做了一个逻辑上的分区啊,那对于这个k stream,我们看到它有两个,除了当前。数据类型不变之外,另外还增加了一个K的类型。其实我们发现它其实也就extend,它也就扩展自继承自data,所以本质上来讲,我们当前还是基于data strip在做各种各样的转换。但是我们需要注意,当前的KBY本质上我们不应该说它是一个算子,因为之前我们的基本转换算子得到的都是一个operator,而现在呢?它相当于只是在当前数据流上追加了一个信息,追加了一个key的信息,按照这个信息做了一个逻辑分区。所以它其实可以说是一个广义上的算子,但它本质上只是做了一个逻辑分区操作而已。
11:04
啊,那。对于这样的一个kid stream呢,接下来我们就会发现。它可以调用的方法里边。熟悉的萨啊,直接求和,或者说max m求最大最小值,或者更加一般化的reduce,这些方法就都出现了,我们就可以真正的去做聚合。所以需要注意,Kid stream本身是flink当中非常重要的一种数据结构,只有基于它才可以做后续的聚合计算,而且那基于这kid stream呢,后边我们还可以去定义一些比较特定的当前算子任务的状态。那么定义出来的这个状态就可以按照当前K去做一个划分,也就是说当前如果说我们单独定义一个状态的话,它就只跟。
12:01
相同的K有关每一个K对应着都有一个状态的实例,那关于状态这一部分知识,我们会在后续章节在第九章去做详细的介绍。
我来说两句