Spark Structured Streaming 读写Kafka与Exactly-once语义

本文总结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,Spark)