本文总结Spark Structured Streaming读写Kafka与Exactly-once语义。
// 读取一个Topic
val inputTable=spark
.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "kafka01:9092,kafka02:9092,kafka03:9092")
.option("subscribe", "topic_1")
.load()
inputTable
.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)")
.as[(String, String)]
// 读取多个Topic
val inputTable=spark
.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "kafka01:9092,kafka02:9092,kafka03:9092")
.option("subscribe", "topic_1,topic_2")
.load()
inputTable
.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)")
.as[(String, String)]
// 读取多个Topic
val inputTable=spark
.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "kafka01:9092,kafka02:9092,kafka03:9092")
.option("subscribePattern", "topic_[1-2]{1}")
.load()
inputTable
.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)")
.as[(String, String)]
批处理适合一次性作业。
// 读取一个Topic
// 默认earliest、latest offset
val inputTable=spark
.read
.format("kafka")
.option("kafka.bootstrap.servers", "kafka01:9092,kafka02:9092,kafka03:9092")
.option("subscribe", "topic_1")
.load()
val resultTable=inputTable
.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)")
.as[(String, String)]
resultTable
.write
.format("console")
.save()
// 读取多个Topic
// 可通过startingOffsets、endingOffsets指定topic partition 的offset
// 注意: 此种方式下,需要指定所有topic partition 的offset。-1: latest -2: earliest
val inputTable=spark
.read
.format("kafka")
.option("kafka.bootstrap.servers", "kafka01:9092,kafka02:9092,kafka03:9092")
.option("subscribe", "topic_1,topic_2")
.option("startingOffsets", """{"topic_1":{"0":13624,"1":-2,"2":-2},"topic_2":{"0":-2,"1":-2,"2":-2}}""")
.option("endingOffsets", """{"topic_1":{"0":13626,"1":13675,"2":-1},"topic_2":{"0":1,"1":-1,"2":-1}}""")
.load()
// 读取多个Topic
// 可通过startingOffsets、endingOffsets指定topic partition 的offset为earliest、latest
val inputTable=spark
.read
.format("kafka")
.option("kafka.bootstrap.servers", "kafka01:9092,kafka02:9092,kafka03:9092")
.option("subscribePattern", "topic_.*")
.option("startingOffsets", "earliest")
.option("endingOffsets", "latest")
.load()
Column | Column Type | 含义 |
---|---|---|
key | binary | Message对应的Key |
value | binary | Message对应的Value |
topic | string | Message对应的Topic |
partition | integer | Message对应的Partition |
offset | long | Message对应的Offset |
timestamp | timestamp | Message对应的时间戳 |
timestampType | integer | Message对应的时间戳类型。0: CreateTime, 1:LogAppendTime。 |
//1)普通字符串
//将字节数据转换为普通字符串
inputTable.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)")
//2)JSON与Avro
当Kafka中的数据为Json或Avro格式的数据时,可用from_json/from_avro抽取需要的字段。
以json为例,如下:
val schema=new StructType()
.add("name",DataTypes.StringType)
.add("age",DataTypes.IntegerType)
val resultTable=inputTable.select(
col("key").cast("string"),
from_json(col("value").cast("string"), schema).as("value")
).select($"value.*")
resultTable.printSchema()
root
|-- name: string (nullable = true)
|-- age: integer (nullable = true)
依靠KafkaConsumerCoordinator
,默认可自动动态发现新增的Topic或Partition。
Spark Streaming对Kafka事务消息的读取没有提供很好的支持。
在Structured Streaming中,设置option("kafka.isolation.level","read_committed")
,可只读取事务成功的消息。默认为read_uncommitted
,读取所有消息,包括事务终止的消息。
Structured Streaming消费Kafka,不需要自己管理Offset,开启Checkpoint后,会将Offset保存到Checkpoint中。
通过maxOffsetsPerTrigger
参数控制每次Trigger最多拉取的记录数。
要写入Kafka的Dataframe应该包含以下列:
key: 可选列。string或binary类型。默认null。
value: 必须列。string或binary类型。
topic: 可选列。string类型。
// 写入单个Topic
// 通过option指定Topic
df
.select($"value")
.writeStream
.format("kafka")
.option("kafka.bootstrap.servers", "kafka01:9092,kafka02:9092,kafka03:9092")
.option("topic", "topic_1")
.option("checkpointLocation", "...")
.outputMode("update")
.trigger(Trigger.ProcessingTime("2 seconds"))
.start()
// 写入多个Topic
// topic取自数据中的topic列
df
.select($"value",$"topic")
.writeStream
.format("kafka")
.option("kafka.bootstrap.servers", "kafka01:9092,kafka02:9092,kafka03:9092")
.option("checkpointLocation", "...")
.outputMode("update")
.trigger(Trigger.ProcessingTime("2 seconds"))
.start()
结合Checkpoint和可重播的Kafka数据源,Structured Streaming处理能保证EOS语义。但写Kafka只能提供At Least Once语义。