繁体   English   中英

如何使用Spark结构化流为Kafka流实现自定义反序列化器?

[英]How to implement custom deserializer for Kafka stream using Spark structured streaming?

我正在尝试将当前的流式应用程序迁移,该应用程序基于使用结构化流式传输的RDD( 从其文档 )到其新的Datasets API,我被告知这是当今使用Spark进行实时流式传输的首选方法。

当前,我有一个应用程序设置要使用一个名为“ SATELLITE”的主题,该主题的消息包含关键时间戳记,值包含Satellite POJO。 但是我在解决如何实现解串器方面遇到问题。 在我当前的应用程序中,这很容易,您只需在喜欢的kafka属性映射中添加一行即可kafkaParams.put("value.deserializer", SatelliteMessageDeserializer.class); 我正在用Java来做这件事,这是最大的挑战,因为所有解决方案似乎都在Scala中,对此我不太了解,而且我不容易将Scala代码转换为Java代码。

我遵循了此问题中概述的JSON示例,该示例当前有效,但对于我需要做的事情似乎过于复杂。 鉴于我已经为此目的定制了反序列化器,所以我不明白为什么我必须首先将其转换为字符串,而只是将其转换为JSON,然后再将其转换为所需的类类型。 我也一直在尝试使用在这里找到的一些示例,但是到目前为止我还没有运气。

目前,我的应用程序如下所示(使用json方法):

import common.model.Satellite;
import org.apache.spark.sql.*;
import org.apache.spark.sql.streaming.StreamingQueryException;
import org.apache.spark.sql.types.DataTypes;
import org.apache.spark.sql.types.StructField;
import org.apache.spark.sql.types.StructType;

public class SparkStructuredStreaming  implements Runnable{

    private String bootstrapServers;
    private SparkSession session;

    public SparkStructuredStreaming(final String bootstrapServers, final SparkSession session) {
        this.bootstrapServers = bootstrapServers;
        this.session = session;
    }
    @Override
    public void run() {
        Dataset<Row> df = session
                .readStream()
                .format("kafka")
                .option("kafka.bootstrap.servers", bootstrapServers)
                .option("subscribe", "SATELLITE")
                .load();

        StructType schema =  DataTypes.createStructType(new StructField[] {
                DataTypes.createStructField("id", DataTypes.StringType, true),
                DataTypes.createStructField("gms", DataTypes.StringType, true),
                DataTypes.createStructField("satelliteId", DataTypes.StringType, true),
                DataTypes.createStructField("signalId", DataTypes.StringType, true),
                DataTypes.createStructField("cnr", DataTypes.DoubleType, true),
                DataTypes.createStructField("constellation", DataTypes.StringType, true),
                DataTypes.createStructField("timestamp", DataTypes.TimestampType, true),
                DataTypes.createStructField("mountPoint", DataTypes.StringType, true),
                DataTypes.createStructField("pseudorange", DataTypes.DoubleType, true),
                DataTypes.createStructField("epochTime", DataTypes.IntegerType, true)
        });

            Dataset<Satellite> df1 = df.selectExpr("CAST(value AS STRING) as message")
                    .select(functions.from_json(functions.col("message"),schema).as("json"))
                    .select("json.*")
                    .as(Encoders.bean(Satellite.class));

        try {
            df1.writeStream()
                    .format("console")
                    .option("truncate", "false")
                    .start()
                    .awaitTermination();

        } catch (StreamingQueryException e) {
            e.printStackTrace();
        }
    }
}

我有一个看起来像这样的自定义解串器

import common.model.Satellite;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.kafka.common.serialization.Deserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

public class SatelliteMessageDeserializer implements Deserializer<Satellite> {

    private static Logger logger = LoggerFactory.getLogger(SatelliteMessageDeserializer.class);
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void configure(Map configs, boolean isKey) {
    }

    @Override
    public void close() {
    }

    @Override
    public Satellite deserialize(String topic, byte[] data) {
        try {
            return objectMapper.readValue(new String(data, "UTF-8"), getMessageClass());
        } catch (Exception e) {
            logger.error("Unable to deserialize message {}", data, e);
            return null;
        }
    }

    protected Class<Satellite> getMessageClass() {
        return Satellite.class;
    }
}

如何在SparkStructuredStreaming类中使用自定义反序列化器? 我正在使用Spark 2.4,OpenJDK 10和Kafka 2.0

编辑:我尝试创建自己的UDF,我认为应该这样做,但是我不确定如何使它返回特定类型,因为它似乎只允许我在其中使用Datatypes类!

UserDefinedFunction mode = udf(
                (byte[] bytes) -> deserializer.deserialize("", bytes), DataTypes.BinaryType //Needs to be type Satellite, but only allows ones of type DataTypes
        );

Dataset df1 = df.select(mode.apply(col("value")));

from_json仅可用于字符串类型的列。

结构化流总是使用Kafka值作为字节

始终使用ByteArrayDeserializer将值反序列化为字节数组。 使用DataFrame操作显式反序列化值

因此,您首先至少要反序列化为String,但是我认为您并不是真的需要它。

可能只是这样做

df.select(value).as(Encoders.bean(Satellite.class))

如果这不起作用,您可以尝试定义自己的UDF / Decoder,以便可以使用SATELLITE_DECODE(value)类的东西。

在斯卡拉

object SatelliteDeserializerWrapper {
    val deser = new SatelliteDeserializer
}
spark.udf.register("SATELLITE_DECODE", (topic: String, bytes: Array[Byte]) => 
    SatelliteDeserializerWrapper.deser.deserialize(topic, bytes)
)

df.selectExpr("""SATELLITE_DECODE("topic1", value) AS message""")

请参阅这篇文章以获取启发 ,并在Databricks博客中提及

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM