在Spark Streaming计算模型下,有时候我们对当前 batch 数据的计算需要依赖上一个 batch 的计算结果,如广告系统中检索日志和曝光时间的join拼接。这时可以使用JavaStreamingContext#remember()
方法完成。该方法需要一个时间参数,用来指定要”记住”多久时间内的 RDD 数据。但是悲剧的是,文档对被”记住”的数据在哪里、如何引用这些数据却没有任何说明,这就导致了使用上的困难。
首先,时间参数的设置是由数据批次的时间间隔决定的。比如,batch interval 为10s, 那么如果你想在这次计算中引用到上次计算中的RDD, 那就必须至少将时间参数设置为 > 10s, 否则还没到下次处理时RDD就过期被GC了。其次,你要自己缓存RDD, spark并没有提供任何访问前批次RDD的方法。缓存的方法,定义一个本地RDD变量lastRdd
,用来保存上个批次你想要保存的计算结果。 但这时候要注意,你的似于 new SparkConf()
, new JavaStreamingContext()
这样的代码是在 driver 节点中执行的,而实际数据处理逻辑是在 executor 所在节点执行的,并不在同一个进程中。所以如果直接定义一个本地RDD变量来引用你的批次数据结果时,那么只有你的 driver 节点中的 executor 进程才有这个引用,数据处理逻代码里中获取不到的。其实,我们可以在transform()
方法中安全的引用到本地缓存RDD的引用变量,因为transform()
永远在 driver 节点执行。因此,只要在该方法中,将当前正在处理的RDD引用直接赋值给lastRdd
,这样在执行下一个批次的任务时,lastRdd
就指向了上一个批次中你想保存的RDD结果了。代码大致如下:
JavaStreamingContext ctx = new JavaStreamingContext(conf, Durations.seconds(10));
// 记住20s
ctx.remember(Durations.seconds(20));
// 用于保存上次RDD对象的引用
final JavaPairRDD<String, ExposeLog>[] lastRdd = new JavaPairRDD[1];
JavaXXXDStream<String, ExposeLog> exposePairStream = exposeStream
.map( ... ... )
.filter( ... ... )
.mapToPair(... ...)
.reduceByKey(... ...)
.transformToPair(new Function<JavaPairRDD<String, ExposeLog>, JavaPairRDD<String, ExposeLog>>() {
@Override
public JavaPairRDD<String, ExposeLog> call(JavaPairRDD<String, ExposeLog> stringExposeLogJavaPairRDD) throws Exception {
if (null == lastRdd[0]) { // 表示当前是第一个批次的数据, 还没有上个批次
lastRdd[0] = stringExposeLogJavaPairRDD; // 将当前批次的rdd保存到引用变量中
return stringExposeLogJavaPairRDD;
}
// 做一些业务逻辑上的处理
stringExposeLogJavaPairRDD = stringExposeLogJavaPairRDD.subtractByKey(lastRdd[0]);
// 将当前批次的rdd保存到引用变量中
lastRdd[0] = stringExposeLogJavaPairRDD;
return stringExposeLogJavaPairRDD;
}
});