本文总结Spark Structured Streaming读写Kafka与Exactly-once语义。
问题一: 读Kafka的方式
// 读取一个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)]
问题二: 读Kafka与批处理
批处理适合一次性作业。
// 读取一个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()
问题三: 读Kafka生成的DataFrame的Schema
| 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。 |
问题四: 读Kafka与反序列化
//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)
问题五: 读Kafka动态发现Topic、Partition
依靠KafkaConsumerCoordinator,默认可自动动态发现新增的Topic或Partition。
问题六: 读Kafka事务消息
Spark Streaming对Kafka事务消息的读取没有提供很好的支持。
在Structured Streaming中,设置option("kafka.isolation.level","read_committed"),可只读取事务成功的消息。默认为read_uncommitted,读取所有消息,包括事务终止的消息。
问题七: 读Kafka的Offset
Structured Streaming消费Kafka,不需要自己管理Offset,开启Checkpoint后,会将Offset保存到Checkpoint中。
问题八: 读Kafka速率限制
通过maxOffsetsPerTrigger参数控制每次Trigger最多拉取的记录数。
问题九: 写Kafka
要写入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()
问题十: 读写Kafka与Exactly Once语义
结合Checkpoint和可重播的Kafka数据源,Structured Streaming处理能保证EOS语义。但写Kafka只能提供At Least Once语义。

本文详细介绍了Spark Structured Streaming如何读写Kafka,并探讨了Exactly-once语义的实现。涵盖读取单个和多个Topic的方法,批处理与实时处理的区别,DataFrame的Schema解析,反序列化技术,动态发现Topic和Partition,事务消息处理,Offset管理,速率限制,以及写入Kafka的策略。
1510

被折叠的 条评论
为什么被折叠?



