本文学习Spark中的Structured Streaming,参见文档Structured Streaming Programming Guide, kafka-integration。
全文目录
- Quick Example
- Programming Model
- Data Sources
- Operations on streaming DataFrames/Datasets
- Starting Streaming Queries
- Manage Streaming Query
- Monitoring Streaming query
- Continuous Processing
- Integration with Kafka
- Checkpoint & WAL
Structured Streaming是一款构建于Spark SQL engine之上的可扩展、容错的stream processing engine。我们可以像在static data上执行batch computation一样执行streaming computation。Spark SQL engine负责增长式、持续的执行并在流数据不断到达时更新最终结果。在不同语言中可以用Dataset/DataFrame API来表示streaming aggregations, event-time windows, stream-to-batch joins等,这些操作实际上运行于Spark SQL engine。
Structured Streaming使用checkpointing和Write-Ahead Logs来保证end-to-end exactly-once fault-tolerance guarantees。
默认情况下,Structured Streaming查询使用micro-batch processing engine,使用一系列小的batch jobs来处理data stream,因此能获得100ms的end-to-end latencies以及exactly-once的容错保障。从Spark 2.3开始,引入一种新的low-latency processing模式,Continuous Processing,能够获得1ms的端到端延迟以及at-least-once的容错保障。在不修改查询使用的Dataset/DataFrame operations的情况下,可以根据需要选择模式。
Quick Example
背景:word count of text data received from a data server listening on a TCP socket
下面是相关的Scala代码。lines表示包含streaming text data的unbounded table,表中包含一个名为value的column。在设置好query之后,便可以开始接收数据。awaitTermination()防止query时停止。
import
上面的代码可以通过创建Spark Application来执行。也可以运行Spark Example。
$ ./bin/run-example org.apache.spark.examples.sql.streaming.StructuredNetworkWordCount localhost 9999
但在执行之前需要先启动生成流数据的server。这里使用Netcat来作为server,启动之后,所有在console中输入的字符都会被放入流中。
$ nc -lk 9999
$ apache spark
$ apache hadoop
+------+-----+
| value|count|
+------+-----+
|apache| 2|
| spark| 1|
|hadoop| 1|
+------+-----+
Programming Model
Structured Streaming的关键思想就是将live data stream当作会持续append数据的table,这样就可以将stream processing model类似于batch processing model。
对于输入的query将会生成result table,每个trigger interval,新行添加进input table并最终更新result table。
Output mode分为3类:
- Complete Mode将更新后的完整result table写入外部存储;
- Append Mode 只将上次更新后append到result table的新行写入外部存储,只适用于result table中已存在row不会被影响的情况;
- Update Mode 只将上次更新后result table中被更新的行写入外部存储,该模式在2.1.1版本后才存在;
下图是example的处理过程,使用complete mode.
注意:Structured streaming并不存储完整的table,只是读取最新的数据来动态更新结果,只会保留少量的中间结果来获得最终结果。
这种模式和其他的stream processing engine差别很大。很多streaming system需要用户自己执行aggregation,需要处理容错和数据一致性。这种模式下,Spark代替用户来更新result table。该模式使用数据中的event time(数据生成的时间)组成time window作为一组进行处理,这样data stream就可以像static dataset一样处理。而且,这种模式天然可以处理late data(获取时间比预期晚的数据),Spark 2.1之后支持watermarking指定late data的阈值,从而允许engine适当清理old state。
Structured streaming设计的关键目标是实现end-to-end exactly-once semantics。为了实现这个目标,要求stream source具有offsets来记录读取的位置,类似于kafka offset。Engine使用checkpointing和write-ahead logs来记录每批次处理数据的offset range。
Data Sources
Spark 2.0之后,DataFrames和Datasets可以同时表示static、bounded的数据以及streaming、unbounded的数据,并使用相同的操作来处理。
Streaming DataFrames可以通过SparkSession.readStream()来创建,返回值实现DataStreamReader接口,可以指定source的详情,包括data format, schema, options。
下面是几种built-in的input sources,用来生成Streaming DataFrames.
- File source 将文件内容作为stream的来源,支持文件格式包括text, csv, json, orc, parquet,参数包括path,maxFilesPerTrigger,latestFirst,fileNameOnly,参见接口DataStreamReader来查看详细信息;默认情况下,使用文件修改时间顺序来读取文件,当指定latestFirst时,顺序逆转。
- Kafka source 从kafka读取数据,兼容Kafka broker versions 0.10.0以及更高,参见 Kafka Integration Guide;
- Socket source (for testing) 从socket connection中读取UTF8 text data,listening server socket处于driver上, 参数包括host, port。注意:该类型只用于测试,并不提供端到端的容错保障;
- Rate source (for testing) 每分钟生成指定行数的数据,每行数据包括时间和值,值记录message的数量, 参数包括rowsPerSecond,rampUpTime,numPartitions;
下面是读取csv格式数据的示例。默认情况下,Structured Streaming使用file作为source需要指定schema。
// Read all the csv files written atomically in a directory
Operations on streaming DataFrames/Datasets
大多数在DataFrame/Dataset上的操作都在streaming中支持。
-Basic operation
case
-Windows operation
Structured streaming模式能够自动处理late data,但由于不能一直保留所有中间状态,Spark 2.1引入watermarking来自动清理old state。对于特定windows,其ending time为T,设置的阈值为late threshold, 当前看到的最大event time Max, 满足关系 Max-threshold>T就可以清理对应windows的状态。
下面示例中,我们使用column timestamp来创建watermark和window。当output mode为update时,超出window的数据不被更新。
import
使用watermark的条件:
- output mode必须是update或append,不能时complete;
- aggregation必须有event-time column或event-time column上的window;
- withWatermark使用的column必须与aggregation使用的column相同,并且withWatermark必须在aggregation之前调用;
注意:watermark能够保证范围内的数据不被删除,但不能保证范围外的数据一定被删除,这样范围外的数据也可能被用于aggregation。
-Join operation
Structured Streaming支持streaming Dataset/DataFrame之间的join以及其与static Dataset/DataFrame之间的join。
下面是Streaming和Static之间的join示例。这种join是无状态的,而且有些类型的outer join目前还不支持。
val
Spark 2.3引入stream-stream join,难点是任何时候join的两边的数据都是不完整的,导致输入之间很难找到match。这种可以通过缓存两边的old data并结合watermark来处理。之间的join可以使用time range join condition(JOIN ON leftTime BETWEEN rightTime AND rightTime + INTERVAL 1 HOUR) 或 event-time windows (JOIN ON leftTimeWindow = rightTimeWindow). 对于inner join可以不指定watermark和约束,但是outer join必须指定,因为它想要生成join时的null。
// Apply watermarks on event-time columns
Join是可以传递的,如df1.join(df2, ...).join(df3, ...).join(df4, ....);Spark 2.4中只能在Append output mode中使用join,其他输出模式下不支持,且在join之前不能使用non-map like操作。
- 去重
流去重可以使用watermark,也可以不使用。
val
有些操作不适用于stream DataFrame/Dataset,如:多个streaming aggregations,获取前N个数据,distinct操作,排序操作也只能在aggregation之后且为complete output mode,有些outer join操作也不支持。有些查询之后需要立即返回结果的操作也不被支持。
Starting Streaming Queries
在定义好stream DataFrame/Dataset之后,便可以开始执行streaming computation,需要使用Dataset.writeStream()返回的DataStreamWriter。
在接口中需要指定下面配置:
- output sink的细节,如格式,location等;
- output mode,指定写入output sink的内容;
- query name, query的唯一识别名称;
- trigger internal,若不指定,系统会在上波数据处理完后获取下一波数据,当trigger time因为上次处理超时错过后,系统会立即触发;
- checkpoint location, 指定系统写checkpoint info的位置,通常为目录。
-output mode
- Append mode (default) : 上次trigger后只有添加进result table的新行写入sink,适用于result table中的数据不会被改动的情况,能够保证每行只被输出一次;
- Complete mode : 在每次trigger后,将整个result table输出到sink,被aggregation操作支持;
- Updated mode: 上次trigger后,result table中被更新的行输出到sink,在Spark 2.1.1后被支持;
-output sinks
有一些built-in的sink,其中console和memory类型用于debug。必须调用start()函数来启动query的执行。
// file sink, store output to a dir
下面列举各种sink的属性。
foreach和foreachBatch允许在streaming的output上执行任意操作,区别时foreach作用于每一个row,而foreachBatch作用于每一个micro-batch。
foreachBatch的输入为Dataset或DataFrame某一个micro-batch的数据batchDF以及batch id。可以用来重新处理每个微批,可以将流数据存放在多个位置,可以在流数据上执行额外的操作。默认情况下,foreachBatch只提供at-least-once的容错保证,但可以通过batchId实现exactly-once。同时,foreachBatch由于使用micro-batch execution的特性,不支持continuous processing mode。
下面是存储多个位置的示例。
streamingDF
-Trigger
Streaming query的trigger设置定义了流数据处理的实时性,表明StreamingQuery生成结果的频率,是micro-batch query还是continuous query。
- 默认是micro-batch query,当上一批次处理完就处理下一批次;
- Fixed interval micro-batches 若设置固定internal,则周期性处理数据,没处理完数据延迟执行,没有新数据则不执行;
- one-time micro-batch 只处理一个batch就终止query,在很多场景下比较节省时间;
- continuous mode 为低延时,持续处理数据,在指定的interval执行异步checkpointing。
import
Manage Streaming Query
Streaming query被启动后会创建StreamingQuery,可以用来管理和监控query。
val
可以在一个SparkSession上启动任意多个query,这些query并行执行并共享集群资源。可以使用sparkSession.streams()来获取StreamingQueryManager,来管理当前active的query。
val
Monitoring Streaming query
有多重方式可以监控active streaming queries,可以使用Spark's Dropwizard Metrics支持来将metrics push到外部系统,也可以编程获取。
可以直接调用streamingQuery.lastProgress(),streamingQuery.status().lastProgress() 来获得StreamingQueryProgress对象,其中包含stream中上一个trigger的所有信息,包括处理的数据,速度,延迟等信息。streamingQuery.recentProgress返回最近几个process组成的数组。streamingQuery.status()返回StreamingQueryStatus,指示立即执行什么query。
val
- 使用异步API来report metrics
下面的示例将监控SparkSession下的所有query,当query启动、停止或处理时都会打印信息。
val
- 使用Dropwizard来report metrics
Spark支持使用Dropwizard Library来report metric,必须显式配置spark.sql.streaming.metricsEnabled为true。
spark.conf.set("spark.sql.streaming.metricsEnabled", "true")
// or
spark.sql("SET spark.sql.streaming.metricsEnabled=true")
当配置被设置后,report metrics将由Dropwizard输出到sinks,像Ganglia, Graphite, JMX, etc。
使用checkpointing和write-ahead logs,能够在出现failure或强制停掉时恢复之前的状态并继续执行。这通过设置checkpoint location来实现,query将保存process的所有信息。checkpoint location必须是HDFS文件系统兼容的路径。
aggDF
Continuous Processing
Continuous Processing是Spark 2.3引入的实验式的streaming执行模式,提供端到端的低延时(~1ms)以及至少一次的容错保障。而默认的micro-batch执行模式提供exactly-once的容错保障但最好延时~100ms。
在continuous processing mode下执行query只需要指定continuous trigger。
import
在spark 2.4中只有下面的操作被continuous processing mode支持:
- Operations只支持map-like Dataset/DataFrame operations,即projections (select,map,flatMap,mapPartitions, etc.) and selections (where,filter, etc.)
- Sources Kafka source支持所有的option
- Sinks Kafka sink支持所有option;Memory sink和Console sink用于debug。
Integration with Kafka
使用Kafka需要引入如下依赖。
groupId = org.apache.spark
artifactId = spark-sql-kafka-0-10_2.12
version = 2.4.5
-Read from kafka
出现failure或强制停掉时恢复之前的状态并继续执行。这通过设置checkpoint location来实现,query将保存process的所有信息。checkpoint location必须HDFS文件系统兼容的路径。
// Read through streaming query
上面的示例中都只监听一个topic,可以监听多个topics。
// subscribe to multiple topics
同时,batch query时指定query offset.
.
source中的每一行包含下面信息。
-Write to kafka
Kafka支持at-least once的写语义,当有些数据被实际记录但没有acknowledge时会出现重复数据,因此有可能存在重复的记录。或许可以使用unique的primary key id来解决这个问题。
写数据到Kafka,包含下面的数据,只有value是必选的,若key不提供,传入null。若配置中设置了topic,将覆盖这里的topic,因此topic也可以省略。当创建Kafka sink时,必须指定kafka.bootstrap.servers。
--
Checkpoint & WAL
为避免内存缓存数据不丢失,引入Checkpoint和Write-ahead log机制来错误恢复。
Checkpoint分为Metadata checkpoint和data checkpoint,前者用于driver挂掉后恢复Streaming context,后者用于处理stateful streaming operation,即后续操作依赖之前的数据。
WAL先将数据存放在日志文件中,然后再对数据进行操作,因此,可以在系统挂掉后,通过日志文件进行错误恢复。像kafka这种具有确认机制的数据源,只有数据写入到WAL后,才向kafka发送确认消息。
启用WAL需要如下配置:
- 为StreamingQuery设置checkpoint目录,用于保存WAL;
- 开启WAL机制,设置spark.streaming.receiver.writeAheadLog.enable为true;
WAL缺点:
- 减少Receiver的吞吐量;
- 数据重复处理;
Checkpoint与cache的区别:cache将数据缓存在内存或磁盘,在rdd操作过程中保存,RDD之间的关系还存在;而checkpoint存放在高容错性的HDFS中,在RDD操作执行结束时保存。