Я использую Spark Structured Streaming (3.2.1) с Kafka.

Я пытаюсь просто прочитать JSON из Kafka, используя определенную схему.

Моя проблема в том, что в определенной схеме я получил ненулевое поле, которое игнорируется, когда я читаю сообщения от Кафки. Я использую функции from_json, которые, похоже, игнорируют тот факт, что некоторые поля не могут быть null.

Вот мой пример кода:

val schemaTest = new StructType()
  .add("firstName", StringType)
  .add("lastName", StringType)
  .add("birthDate", LongType, nullable = false)

val loader =
    spark
    .readStream
    .format("kafka")
    .option("startingOffsets", "earliest")
    .option("kafka.bootstrap.servers", "BROKER:PORT")
    .option("subscribe", "TOPIC")
    .load()

val df = loader.
     selectExpr("CAST(value AS STRING)")
    .withColumn("value", from_json(col("value"), schemaTest))
    .select(col("value.*"))

df.printSchema()

val q = df.writeStream
  .format("console")
  .option("truncate","false")
  .start()
  .awaitTermination()

Я получил это, когда печатаю схему df, которая отличается от моей schemaTest:

root
 |-- firstName: string (nullable = true)
 |-- lastName: string (nullable = true)
 |-- birthDate: long (nullable = true)

И полученные данные такие:

+---------+--------+----------+
|firstName|lastName|birthDate |
+---------+--------+----------+
|Toto     |Titi    |1643799912|
+---------+--------+----------+
|Tutu     |Tata    |null      |
+---------+--------+----------+

Мы также пытаемся добавить возможность изменить режим в функции from_json с одного по умолчанию PERMISSIVE на другие (DROPMALFORMED, FAILFAST), но на самом деле вторая запись, которая не учитывает определенная схема просто не считается поврежденной, поскольку поле birthDate допускает значение NULL.

Возможно я что-то упустил, но если это не так, то у меня возникли следующие вопросы.

Вы знаете, почему схема печати df не похожа на мою schemaTest? (с полем, не допускающим значение NULL)

А также, как я могу управлять ненулевым значением в моем случае? Я знаю, что могу фильтровать, но я хотел бы знать, есть ли альтернатива, использующая схему, как она должна работать. А также, не совсем просто отфильтровать, если у меня есть схема с большим количеством полей, не обнуляемых.

2
MaxP 2 Фев 2022 в 19:07

2 ответа

На самом деле это предполагаемое поведение функции from_json. Вы можете прочитать следующее из исходный код:

// The JSON input data might be missing certain fields. We force the nullability
// of the user-provided schema to avoid data corruptions. In particular, the parquet-mr encoder
// can generate incorrect files if values are missing in columns declared as non-nullable.
val nullableSchema = schema.asNullable

override def nullable: Boolean = true

Если у вас есть несколько обязательных полей, вы можете создать выражение фильтра из вашего schemaTest (или списка столбцов) и использовать его следующим образом:

val filterExpr = schemaTest.fields
  .filter(!_.nullable)
  .map(f => col(f.name).isNotNull)
  .reduce(_ and _)

val df = loader
  .selectExpr("CAST(value AS STRING)")
  .withColumn("value", from_json(col("value"), schemaTest))
  .select(col("value.*"))
  .filter(filterExpr) 
0
blackbishop 2 Фев 2022 в 21:46

Я хотел бы предложить другой способ сделать:

 def isCorrupted(df: DataFrame): DataFrame = {
  val filterNullable = schemaTest
    .filter(e => !e.nullable)
    .map(_.name)

  filterNullable.foldLeft(df) { case ((accumulator), (columnName)) =>
      accumulator.withColumn("isCorrupted", when(col(columnName).isNull, 1).otherwise(0))
  } 
  .filter(col("isCorrupted") === lit(0)) 
  .drop(col("isCorrupted"))
  
}

val df = loader
  .selectExpr("CAST(value as STRING)")
  .withColumn("value", from_json(col("value"), schemaTest))
  .select(col("value.*"))
  .transform(isCorrupted)

0
Tyron Ferreira 3 Фев 2022 в 11:59