简介
数据可以分为两大类:维度数据和事实数据。在数据仓库中最常用的是星型结构(即1事实表 + N维表),因此在处理过程中,关联维表是不可避免的。通常需要将事实数据与维度数据关联,以获取想要的维度列。
在批处理类计算任务中,只要在需要时将维度数据读取出来然后按正常的计算流程去关联即可。但是在流式实时任务中,如果还是在每个batch中都去读维度数据,则会导致大量的冗余读,并且当维表数据量比较大时,带来的计算延时是不可接受的,会导致实时任务堆积。
在流式实时任务中支持维度数据的缓存获取,是一项必备的功能。有必要从框架层次提供一个简单方便的维度数据获取接口,来获取最新有效的维度数据给用户代码使用。
设计
维度数据是一个全局的配置信息,其特点是数据量相对不大,而且数据是缓慢变化的,更新频率不高。因此自然想到的是可以缓存到executor内存中,并根据其缓慢变化的特性,对其进行不断地更新。
常规方法
通常的实现方式可以利用Spark Streaming中的transform操作来实现。查看官方文档可以看到transform操作能实现任何将RDD转化为另一个RDD的操作,并在每个批次间会调用此转化操作(实际是在TransformedDStream.compute()方法即生成batch数据中被调用)。可以在transform中进行随时间变化的RDD操作,即RDD操作,分区数,广播变量等可以在批次之间更改。
那么可以维护一个全局的维度数据缓存,并在tranform算子中进行更新,示例如下:
// 得到下次数据更新的时间
def nextDeadline() : Long = {
LocalDate.now.atStartOfDay().plusDays(1).toInstant(ZoneOffset.UTC).toEpochMilli()
}
// 存储全局维度数据的可变引用
var initRDD = sparkSession.read.parquet("/tmp/learningsparkstreaming/dimension.parquet")
// 存储全局的下次数据更新时间的可变引用
var _nextDeadline = nextDeadline()
// 得到DStream数据流
val lines = ssc.socketTextStream("localhost", 9999)
// 在transform中关联维度数据,并更新维度数据
val linesTransform = lines.transform(rdd => {
if (System.currentTimeMillis > _nextDeadline