在 Structured Streaming 中, 可以按照事件发生时的时间对数据进行聚合操作, 即基于 event-time 进行操作。在这种机制下, 即不必考虑 Spark 陆续接收事件的顺序是否与事件发生的顺序一致, 也不必考虑事件到达 Spark 的时间与事件发生时间的关系。因此, 它在提高数据处理精度的同时, 大大减少了开发者的工作量。
我们现在想计算 10 分钟内的单词, 每 5 分钟更新一次, 也就是说在 10 分钟窗口 12:00 - 12:10, 12:05 - 12:15, 12:10 - 12:20等之间收到的单词量。注意, 12:00 - 12:10 表示数据在 12:00 之后 12:10 之前到达.
现在,考虑一下在 12:07 收到的单词。单词应该增加对应于两个窗口12:00 - 12:10和12:05 - 12:15的计数。因此,计数将由分组键(即单词)和窗口(可以从事件时间计算)索引。
import org.apache.spark.api.java.function.FlatMapFunction;
import org.apache.spark.sql.*;
import org.apache.spark.sql.streaming.StreamingQueryException;
import scala.Tuple2;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeoutException;
public class Test_10_Window {
public static void main(String[] args) throws TimeoutException, StreamingQueryException {
// 创建一个SparkSession对象,用于与Apache Spark进行交互和分析
SparkSession spark = SparkSession
.builder()
.appName("window-demo")
.master("local[*]")
.getOrCreate();
// 以socket方式从指定主机和端口读取数据,并将数据转换为DataFrame,同时给数据添加时间戳。
Dataset lines = spark.readStream()
.format("socket") // 设置数据源
.option("host", "localhost")
.option("port", 9999)
// 给产生的数据自动添加时间戳
.option("includeTimestamp", true)
.load();
// (a,a,a,),2024-01-01)
// lines中的元素转换为元组,每个元组的第一个元素是字符串类型,第二个元素是时间戳类型。使用Encoders来指定元素的编码方式。
Dataset> inputLines = lines
.as(Encoders.tuple(Encoders.STRING(), Encoders.TIMESTAMP()));
// RowDataset转换为元组格式,其中包含两个元素:一个字符串类型和一个时间戳类型。
// ((a,a,a,),2024-01-01)---->(a,2024-01-01),
Dataset> wordsWithTs = inputLines.flatMap(
new FlatMapFunction, Tuple2>() {
@Override
public Iterator> call(Tuple2 tuple) throws Exception {
// 正则表达式 "\W+" 匹配一个或多个非单词字符。
String[] words = tuple._1.split("\\W+");
Timestamp timestamp = tuple._2;
List> outputList = new ArrayList<>();
for (String word : words) {
outputList.add(new Tuple2<>(word, timestamp));
}
return outputList.iterator();
}
},
Encoders.tuple(Encoders.STRING(), Encoders.TIMESTAMP()));
// 函数将一个包含单词和时间戳的数据集转换为DataFrame,其中DataFrame的列包括"word"和"timestamp"。
Dataset words = wordsWithTs.toDF("word", "timestamp");
// 将"word"列和"timestamp"列按时间窗口划分数据,同时按单词内容对数据进行分组。
// 例如,对于时间窗口(2024-01-01,2024-01-02),如果"word"列的值为"a",则该行数据将被添加到时间窗口(a,2024-01-01)中。如果"word"列的值为"b",则该行数据将被添加到时间窗口(b,2024-01-01)中。
Dataset windowedCounts = words.groupBy(
// 对时间窗口分组。这个窗口的大小为4分钟,滑动步长为2分钟,即每次窗口向前滑动2分钟,
functions.window(words.col("timestamp"), "4 minutes", "2 minutes"),
// 还按照"word"列进行分组,按时间窗口划分数据,还会进一步在每个时间窗口内按照单词内容细分。
words.col("word"))
// 对满足同一组条件的数据进行计数操作,得到的结果是一个新的Dataset,
// 其中每一行表示一个特定的时间窗口和单词组合及其在该窗口内出现的次数。
.count()
.orderBy("window");
wordsWithTs.writeStream()
.format("console")
.outputMode("complete")
.option("truncate", false)
.start()
.awaitTermination();
}
}
测试
nc -lk 9999
24/01/28 14:09:18 INFO WriteToDataSourceV2Exec: Data source write support MicroBatchWrite[epoch: 5, writer: ConsoleWriter[numRows=20, truncate=false]] committed.
-------------------------------------------
Batch: 5
-------------------------------------------
+------------------------------------------+----+-----+
|window |word|count|
+------------------------------------------+----+-----+
|{2024-01-28 14:04:00, 2024-01-28 14:08:00}|a |4 |
|{2024-01-28 14:04:00, 2024-01-28 14:08:00}|d |2 |
|{2024-01-28 14:04:00, 2024-01-28 14:08:00}|f |14 |
|{2024-01-28 14:06:00, 2024-01-28 14:10:00}|a |4 |
|{2024-01-28 14:06:00, 2024-01-28 14:10:00}|b |15 |
|{2024-01-28 14:06:00, 2024-01-28 14:10:00}|f |14 |
|{2024-01-28 14:06:00, 2024-01-28 14:10:00}|d |2 |
|{2024-01-28 14:08:00, 2024-01-28 14:12:00}|b |15 |
+------------------------------------------+----+-----+
24/01/28 14:09:18 INFO CheckpointFileManager: Writing atomically to file:/private/var/folders/1t/48p3vpm91zxb9mgbd9db59yw0000gn/T/temporary-6864af8e-ff52-42cc-84a5-f53379878194/commits/5 using temp file file:/private/var/folders/1t/48p3vpm91zxb9mgbd9db59yw0000gn/T/temporary-6864af8e-ff52-42cc-84a5-f53379878194/commits/.5.6a7d41e5-bc40-418c-86bd-4e3bace4bc7c.tmp
24/01/28 14:09:18 INFO CheckpointFileManager: Renamed temp file file:/private/var/folders/1t/48p3vpm91zxb9mgbd9db59yw0000gn/T/temporary-6864af8e-ff52-42cc-84a5-f53379878194/commits/.5.6a7d41e5-bc40-418c-86bd-4e3bace4bc7c.tmp to file:/private/var/folders/1t/48p3vpm91zxb9mgbd9db59yw0000gn/T/temporary-6864af8e-ff52-42cc-84a5-f53379878194/commits/5
24/01/28 14:09:18 INFO MicroBatchExecution: Streaming query made progress: {