PySpark:反序列化eventhub捕获avro文件中包含的Avro序列化消息

时间:2018-11-07 21:06:57

标签: apache-spark pyspark avro azure-eventhub-capture

初始情况

AVRO序列化事件发送到天蓝色事件中心。这些事件使用天蓝色事件中心捕获功能持久存储。捕获的数据以及事件中心元数据均以Apache Avro格式编写。捕获的avro文件中包含的原始事件应使用(py)Spark进行分析。


问题

如何使用(py)Spark反序列化包含在AVRO文件的字段/列中的AVRO序列化事件? (注释:该事件的平均模式无法被阅读器应用程序识别,但是它作为avro标头包含在消息中)


背景

背景是用于IoT场景的分析平台。消息由在kafka上运行的IoT平台提供。为了更灵活地更改模式,战略决策是坚持使用avro格式。 为了启用Azure流分析(ASA)的使用,每条消息都指定了avro架构(否则ASA无法反序列化该消息)。

捕获文件avro模式

事件中心捕获功能生成的avro文件的架构如下所示:

{
    "type":"record",
    "name":"EventData",
    "namespace":"Microsoft.ServiceBus.Messaging",
    "fields":[
                 {"name":"SequenceNumber","type":"long"},
                 {"name":"Offset","type":"string"},
                 {"name":"EnqueuedTimeUtc","type":"string"},
                 {"name":"SystemProperties","type":{"type":"map","values":["long","double","string","bytes"]}},
                 {"name":"Properties","type":{"type":"map","values":["long","double","string","bytes"]}},
                 {"name":"Body","type":["null","bytes"]}
             ]
}

(请注意,实际消息以字节为单位存储在主体字段中)

示例事件平均模式

为说明起见,我将具有以下avro模式的事件发送到事件中心:

{
    "type" : "record",
    "name" : "twitter_schema",
    "namespace" : "com.test.avro",
    "fields" : [ 
                {"name" : "username","type" : "string"}, 
                {"name" : "tweet","type" : "string"},
                {"name" : "timestamp","type" : "long"}
    ],
}

示例事件

{
    "username": "stackoverflow",
    "tweet": "please help deserialize me",
    "timestamp": 1366150681
}

示例Avro邮件有效载荷

(编码为字符串/请注意,其中包含avro模式)

Objavro.schema�{"type":"record","name":"twitter_schema","namespace":"com.test.avro","fields":[{"name":"username","type":"string"},{"name":"tweet","type":"string"},{"name":"timestamp","type":"long"}]}

因此,最后,此有效负载将作为字节存储在捕获avro文件的“正文”字段中。



我目前的做法

为便于使用,测试和调试,我目前使用pyspark jupyter笔记本。

Spark会话的配置:

%%configure
{
    "conf": {
        "spark.jars.packages": "com.databricks:spark-avro_2.11:4.0.0"
    }
}

将avro文件读取到数据框中并输出结果:

capture_df = spark.read.format("com.databricks.spark.avro").load("[pathToCaptureAvroFile]")
capture_df.show()

结果:

+--------------+------+--------------------+----------------+----------+--------------------+
|SequenceNumber|Offset|     EnqueuedTimeUtc|SystemProperties|Properties|                Body|
+--------------+------+--------------------+----------------+----------+--------------------+
|            71|  9936|11/4/2018 4:59:54 PM|           Map()|     Map()|[4F 62 6A 01 02 1...|
|            72| 10448|11/4/2018 5:00:01 PM|           Map()|     Map()|[4F 62 6A 01 02 1...|

获取“正文”字段的内容并将其转换为字符串:

msgRdd = capture_df.select(capture_df.Body.cast("string")).rdd.map(lambda x: x[0])

这就是我使代码起作用的程度。花了很多时间尝试反序列化实际消息,但没有成功。我将不胜感激!

一些其他信息: Spark在Microsoft Azure HDInsight 3.6群集上运行。 Spark版本为2.2。 Python版本是2.7.12。

3 个答案:

答案 0 :(得分:0)

您要执行的操作是将.decode('utf-8')应用于“正文”列中的每个元素。您必须从解码创建一个UDF,然后才能应用它。可以使用以下格式创建UDF

from pyspark.sql import functions as f

decodeElements = f.udf(lambda a: a.decode('utf-8'))

这是解析the IoT Hub to a custom Blob Storage endpoint存储的avro文件的完整示例:

storage_account_name = "<YOUR STORACE ACCOUNT NAME>"
storage_account_access_key = "<YOUR STORAGE ACCOUNT KEY>"

# Read all files from one day. All PartitionIds are included. 
file_location = "wasbs://<CONTAINER>@"+storage_account_name+".blob.core.windows.net/<IoT Hub Name>/*/2018/11/30/*/*"
file_type = "avro"

# Read raw data
spark.conf.set(
  "fs.azure.account.key."+storage_account_name+".blob.core.windows.net",
  storage_account_access_key)

reader = spark.read.format(file_type).option("inferSchema", "true")
raw = reader.load(file_location)

# Decode Body into strings
from pyspark.sql import functions as f

decodeElements = f.udf(lambda a: a.decode('utf-8'))

jsons = raw.select(
    raw['EnqueuedTimeUtc'],
    raw['SystemProperties.connectionDeviceId'].alias('DeviceId'), 
    decodeElements(raw['Body']).alias("Json")
)

# Parse Json data
from pyspark.sql.functions import from_json

json_schema = spark.read.json(jsons.rdd.map(lambda row: row.Json)).schema
data = jsons.withColumn('Parsed', from_json('Json', json_schema)).drop('Json')

Disclamer:我是Python和Databricks的新手,我的解决方案可能不够完美。但是我花了一天多的时间来完成这项工作,我希望这对某人来说可以是一个很好的起点。

答案 1 :(得分:0)

我想你也可以做类似的事情:

jsonRdd = raw.select(raw.Body.cast("string"))

答案 2 :(得分:0)

我有同样的问题。

Spark 2.4版本为我解决了这个问题。

您可以在这里找到文档:https://databricks.com/blog/2018/11/30/apache-avro-as-a-built-in-data-source-in-apache-spark-2-4.html

备注:您需要知道您的AVRO文件是如何创建架构的(只需将其加载到此处)。

缺点:目前仅在Scala和Java中可用。据我所知,在Python中还不可能。