Join Operations
自Spark-2.0 Structured Streaming引入的join的概念(inner和一些外连接)。支持和静态或者动态的Dataset/DataFrame做join操作。
Stream-static Joins
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder
.appName("StructuredNetworkWordCount")
.master("local[*]")
.getOrCreate()
spark.sparkContext.setLogLevel("ERROR")
import spark.implicits._
val userDF = spark.sparkContext.parallelize(List((1,"zs"),(2,"lisi")))
.toDF("id","name")
//1 apple 1 45
val lineDF = spark.readStream.format("socket")
.option("host", "train")
.option("port", 9999)
.load()
val orderItemDF = lineDF.as[String]
.map(t=>{
val tokens = t.split(" ")
(tokens(0).toInt,tokens(1),(tokens(2).toInt)*(tokens(3).toDouble))
}).toDF("uid","item","cost")
import org.apache.spark.sql.functions._
val joinDF = orderItemDF.join(userDF, $"id" === $"uid")
.groupBy("uid", "name")
.agg(sum("cost").as("total_cost"))
//3.产生StreamQuery对象
val query:StreamingQuery = joinDF.writeStream
.outputMode(OutputMode.Update())
.format("console")
.start()
query.awaitTermination()
}
注意: stream-static joins并不是状态的,所以不需要做状态管理,目前还有一些外连接还不支持,目前只支持inner
和left outer
。
Stream-Stream Joins
在Spark-2.3添加了streaming-streaming的支持,实现两个流的join最大的挑战是在于找到一个时间点实现两个流的join,因为这两个流都没有结束。任意一个接受的流可以匹配另一个流中即将被接受的数据。所以在任意一个流中我们需要接收并将这些数据进行缓存,然后作为当前stream的状态,然后去匹配另外一个流的后续接收的数据,继而生成相应的join的结果集。和Streaming的聚合很类似我们使用watermarker处理late,乱序的数据,限制状态的使用。
内连接可以使用任意一些column作为连接条件,然而在Stream计算开始运行的时候,流计算的状态会持续的增长,因为必须存储所有传递过来的状态数据,然后和后续的新接受的数据做匹配。为了避免无限制的状态存储。一般需要定义额外的join的条件。例如限制一些old数据如果和新数据时间间隔大于某个阈值就不能匹配。因此可以删除这些陈旧的状态。简单来说需要做以下步骤:
- 两边流计算需要定义watermarker延迟,这样系统可以知道两个流的时间差值。
- 定制以下event time的限制条件,这样引擎可以计算出哪些数据old的不再需要了。可以使用以下两种方式定制
- 时间范围界定例如:
JOIN ON leftTime BETWEEN rightTime AND rightTime + INTERVAL 1 HOUR
- 基于Event-time Window例如:
JOIN ON leftTimeWindow = rightTimeWindow
案例1(Range案例)
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder
.appName("StructuredNetworkWordCount")
.master("local[*]")
.getOrCreate()
spark.sparkContext.setLogLevel("ERROR")
import spark.implicits._
//001 apple 1 4.5 1566113401000
//创建DF
val orderDF = spark.readStream
.format("socket")
.option("host", "train")
.option("port", 9999)
.load()
.