如何在reduceByKeyAndWindow()中为特定用例实现invFunc

时间:2015-10-18 11:57:55

标签: java apache-spark spark-streaming

我正在使用spark streaming来处理文件流。多个文件到达批处理并从所有文件中激发过程数据。 我的用途是获取后续批次中文件的每条记录的总和。例如:

  • key:key_1值:10 - > BATCH1
  • key:key_1 value:05 - > BATCH1
  • key:key_1值:19 - > batch2
  • key:key_1值:11 - > batch3
  • key:key_1值:10 - > batch4

我需要输出如下内容:

  • 处理第一批后,我需要输出=> key:key_1 val:15
  • 处理完第二批后我需要输出=> key:key_1 val:34
  • 处理完第3批后,我需要输出=> key:key_1 val:45
  • 处理完第4批后,我需要输出=> key:key_1 val:55
  • 处理完第5批后,我需要输出=> key:key_1 val:55

我的 reduceByKeyAndWindow()的代码如下:

JavaPairDStream<String, Summary> grpSumRDD = sumRDD.reduceByKeyAndWindow(GET_GRP_SUM, Durations.minutes(2*batchInterval), Durations.minutes(batchInterval));

private static final Function2<Summary, Summary, Summary> GET_GRP_SUM = new Function2<Summary, Summary, Summary>() {
    private static final long serialVersionUID = 1L;

    public Summary call(Summary s1, Summary s2) throws Exception {
        try {

            Summary s = new Summary();

            long grpCnt = s1.getDelta() + s2.getDelta();
            s.setDeltaSum(grpCnt);

            return s;
        } catch (Exception e) {
            logger.error(" ==== error in CKT_GRP_SUM ==== :"+e);
            return new Summary();
        }
    }

};

我从上面的实现获得的输出如下:

  • 处理完第一批后,我得到输出=&gt; key:key_1值:15
  • 处理完第二批后,我得到输出=&gt; key:key_1值:34
  • 处理第3批后,我得到输出=&gt; key:key_1值:30
  • 处理完第4批后,我得到输出=&gt; key:key_1值:21
  • 处理完第5批后,我得到输出=&gt; key:key_1值:10

根据reduceByKeyAndWindow()的输出,它似乎正在计算先前批次数据和当前批次数据的聚合。 但我的要求是对先前批次的聚合数据和当前批次数据进行聚合。所以按照上面的说法 它应该在第4批和第5批结束时以[(((15)+19)+11)+10 = 55]输出。

我读到 reduceByKeyAndWindow(),可以实现 invFunc 来获得预期的输出。我尝试实现它类似于 GET_GRP_SUM ,但它没有给我预期的结果。任何帮助正确实现以获得所需的输出将不胜感激。

我正在使用java 1.8.45和spark版本1.4.1以及hadoop版本2.7.1。

我使用reduceByKeyAndWindow()

在invFunc上实现
JavaPairDStream<String, Summary> grpSumRDD = sumRDD.reduceByKeyAndWindow(GET_GRP_SUM, INV_GET_GRP_SUM, Durations.minutes(2*batchInterval), Durations.minutes(batchInterval));

private static final Function2<Summary, Summary, Summary> INV_GET_GRP_SUM = new Function2<Summary, Summary, Summary>() {
    private static final long serialVersionUID = 1L;

    public Summary call(Summary s1, Summary s2) throws Exception {
        try {

            Summary s = new Summary();

            long grpCnt = s1.getDelta() + s2.getDelta();
            s.setDeltaSum(grpCnt);

            return s;

        } catch (Exception e) {
            logger.error(" ==== error in INV_GET_GRP_SUM ==== :"+e);
            return new Summary();
        }
    }
};

我已经像上面那样实现了我的invFunc,这并没有给我预期的输出。我在这里分析的是,s1和s2给了我以前的批次聚合值,以为我不太确定。

我尝试更改我的invFunc实现,如下所示:

private static final Function2<Summary, Summary, Summary> INV_GET_GRP_SUM = new Function2<Summary, Summary, Summary>() {
    private static final long serialVersionUID = 1L;

    public Summary call(Summary s1, Summary s2) throws Exception {
        try {

            return s1;

        } catch (Exception e) {
            logger.error(" ==== error in INV_GET_GRP_SUM ==== :"+e);
            return new Summary();
        }
    }
};

这个实现给了我预期的输出。但是我遇到的问题是使用invFunc的reduceByKeyAndWindow()不会自动删除旧键。我经历了几个帖子,我发现我需要编写自己的过滤器函数,它将删除0值(没有值)的旧键。

我再次不确定如何编写过滤器函数来删除0值(没有值)的旧键,因为我没有具体了解返回到INV_GET_GRP_SUM的s1和s2。

1 个答案:

答案 0 :(得分:1)

使用UpdateStateByKey

您是否从Streaming API检出了updateStateByKey()?它允许您在批处理间隔之间维护键值对的状态,不断使用与其关联的新信息(值)更新每个键。这适用于您的用例,因为先前的数据状态将包含每个键的聚合总和,直到最新状态。有关此功能的更多信息,请参见其使用here和示例here

关于该功能的一个注意事项是它需要启用检查点,以便可以在每次迭代时保存状态。

(编辑:)

使用ReduceByKeyAndWindow

关于使用reduceKeyAndWindow(),正常 func call()方法和 invFunc 的第二个参数是新元素添加并分别减去旧元素。从本质上讲,您通过在新的时间片段(使用GET_GRP_SUM进行添加)中添加元素并从旧时间片中删除元素(您不使用INV_GET_GRP_SUM来实现此窗口缩减。 )。请注意,在第一次尝试时,您将旧值重新添加回当前窗口中的值,并且在第二次尝试中,您忽略了移出窗口的值。

要从移出窗口的元素中减去旧值,您可能希望INV_GET_GRP_SUM具有与下面类似的逻辑(并且可以找到类似的正确实现here):

public Summary call(Summary s1, Summary s2) throws Exception {
    try {

        long grpCnt = s1.getDelta() - s2.getDelta();
        s.setDeltaSum(grpCnt);

    } catch (Exception e) {
        logger.error(" ==== error in INV_GET_GRP_SUM ==== :"+e);
        return new Summary();
    }
}

对于你的另一个问题,似乎有一种方法可以过滤掉过期的密钥,正如你所提到的,它确实涉及编写过滤函数。正如您在API中看到的,此过滤器函数接受您的键值对并返回一个布尔值,该布尔值将设置为true(如果您想保留该对)或false(如果您要删除对)。在这种情况下,由于您希望在值达到零时删除您的对,您可以执行以下操作:

private static final Function<scala.Tuple2<String, Summary>, Boolean> FILTER_EXPIRED = new Function<scala.Tuple2<String, Summary>, Boolean>() {
    public Boolean call(scala.Tuple2<String, Summary> s) { 
        return s.productElement(1) > 0; 
    }
}

然后您可以将其传递到reduceByKeyAndWindow()函数中(请注意,您应该在此处传递partition参数以确定您的DStream中的RDD将使用多少个分区):

JavaPairDStream<String, Summary> grpSumRDD = sumRDD.reduceByKeyAndWindow(GET_GRP_SUM, INV_GET_GRP_SUM, Durations.minutes(2*batchInterval), Durations.minutes(batchInterval), partitions, FILTER_EXPIRED);