這是一篇翻譯,原文來自:How to load some Avro data into Spark。
首先,爲什麼使用 Avro ?
最基本的格式是 CSV ,其廉價並且不需要頂一個一個 schema 和數據關聯。
隨後流行起來的一個通用的格式是 XML,其有一個 schema 和 數據關聯,XML 廣泛的使用於 Web Services 和 SOA 架構中。不幸的是,其非常冗長,並且解析 XML 需要消耗內存。
另外一種格式是 JSON,其非常流行易於使用因爲它非常方便易於理解。
這些格式在 Big Data 環境中都是不可拆分的,這使得他們難於使用。在他們之上使用一個壓縮機制(Snappy,Gzip)並不能解決這個問題。
因此不同的數據格式出現了。Avro 作爲一種序列化平臺被廣泛使用,因爲它能跨語言,提供了一個小巧緊湊的快速的二進制格式,支持動態 schema 發現(通過它的泛型)和 schema 演變,並且是可壓縮和拆分的。它還提供了複雜的數據結構,例如嵌套類型。
例子
讓我們來看一個例子,創建一個 Avro schema 並生成一些數據。在一個真實案例的例子中,組織機構通常有一些更加普通的格式,例如 XML,的數據,並且他們需要通過一些工具例如 JAXB 將他們的數據轉換成 Avro。我們來使用這個例子,其中 twitter.avsc 如下:
{
"type" : "record",
"name" : "twitter_schema",
"namespace" : "com.miguno.avro",
"fields" : [
{ "name" : "username",
"type" : "string",
"doc" : "Name of the user account on Twitter.com" },
{
"name" : "tweet",
"type" : "string",
"doc" : "The content of the user's Twitter message" },
{
"name" : "timestamp",
"type" : "long",
"doc" : "Unix epoch time in seconds" }
],
"doc:" : "A basic schema for storing Twitter messages"
}
twitter.json 中有一些數據:
{"username":"miguno","tweet":"Rock: Nerf paper, scissors is fine.","timestamp": 1366150681 }
{"username":"BlizzardCS","tweet":"Works as intended. Terran is IMBA.","timestamp": 1366154481 }
我們將這些數據轉換成二進制的 Avro 格式:
$ java -jar ~/avro-tools-1.7.7.jar fromjson --schema-file twitter.avsc twitter.json > twitter.avro
然後,我們將 Avro 數據轉換爲 Java:
$ java -jar /app/avro/avro-tools-1.7.7.jar compile schema /app/avro/data/twitter.avsc /app/avro/data/
現在,我們編譯這些類並將其打包:
$ CLASSPATH=/app/avro/avro-1.7.7-javadoc.jar:/app/avro/avro-mapred-1.7.7-hadoop1.jar:/app/avro/avro-tools-1.7.7.jar
$ javac -classpath $CLASSPATH /app/avro/data/com/miguno/avro/twitter_schema.java
$ jar cvf Twitter.jar com/miguno/avro/*.class
我們啓動 Spark,並將上面創建的 Jar 和一些需要的庫(Hadoop 和 Avro)傳遞給 Spark 程序:
$ ./bin/spark-shell --jars /app/avro/avro-mapred-1.7.7-hadoop1.jar,/avro/avro-1.7.7.jar,/app/avro/data/Twitter.jar
在 REPL 中,我們獲取數據並創建一個 RDD:
scala>
import com.miguno.avro.twitter_schema
import org.apache.avro.file.DataFileReader;
import org.apache.avro.file.DataFileWriter;
import org.apache.avro.io.DatumReader;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.specific.SpecificDatumReader;
import org.apache.avro.mapreduce.AvroKeyInputFormat
import org.apache.avro.mapred.AvroKey
import org.apache.hadoop.io.NullWritable
import org.apache.avro.mapred.AvroInputFormat
import org.apache.avro.mapred.AvroWrapper
import org.apache.avro.generic.GenericRecord
import org.apache.avro.mapred.{AvroInputFormat, AvroWrapper}
import org.apache.hadoop.io.NullWritable
val path = "/app/avro/data/twitter.avro"
val avroRDD = sc.hadoopFile[AvroWrapper[GenericRecord], NullWritable, AvroInputFormat[GenericRecord]](path)
avroRDD.map(l => new String(l._1.datum.get("username").toString() ) ).first
返回結果:
res2: String = miguno
一些注意事項:
- 我們在使用 MR1 的類,但是 MR2的類同樣能夠運行。
- 我們使用GenericRecord 而不是 Specific ,因爲我們生成了 Avro schema(並且導入了它)。更多內容參見http://avro.apache.org/docs/current/gettingstartedjava.html
- 注意到即使 Avro 類是用 Java 編譯的,你還是可以在 Spark 中導入他們,因爲 Scala 也是運行在 JVM 之上。
- Avro 允許你定義一個可選的方式去定義 schema 中每個節點的反序列化類型,即通過 key/value 的鍵值對,這是方式非常方便。參考http://stackoverflow.com/questions/27827649/trying-to-deserialize-avro-in-spark-with-specific-type/27859980?noredirect=1%23comment44240726_27859980 。
- 還有大量的其他方式來實現這個功能,一種是使用 Kryo,另一種是使用 Spark SQL。然而,這需要你創建一個 Spark SQL 的上下文(見https://github.com/databricks/spark-avro ),而不是一個純粹的 Spark/Scala 方式。然而,也許這在將來會是一種最佳方式?
翻譯結束。
接下來,我將上述過程在 CDH 5.3 集羣中測試一遍。
驗證
首先,在集羣一個節點創建 twitter.avsc 和 twitter.json 兩個文件。
然後,使用 avro-tools 將這些數據轉換成二進制的 Avro 格式:
$ java -jar /usr/lib/avro/avro-tools.jar fromjson --schema-file twitter.avsc twitter.json > twitter.avro
這時候會生成 avro 文件:
$ ll
總用量 12
-rw-r--r-- 1 root root 543 3月 25 15:13 twitter.avro
-rw-r--r-- 1 root root 590 3月 25 15:12 twitter.avsc
-rw-r--r-- 1 root root 191 3月 25 15:12 twitter.json
將 Avro 數據轉換爲 Java:
$ java -jar /usr/lib/avro/avro-tools.jar compile schema twitter.avsc .
這時候會生成 twitter_schema.java 文件:
$ tree
.
├── com
│ └── miguno
│ └── avro
│ └── twitter_schema.java
├── twitter.avro
├── twitter.avsc
└── twitter.json
這時候會生成一個 Twitter.jar 的 jar 包。
編譯這些類並將其打包:
$ CLASSPATH=/usr/lib/avro/avro-mapred-hadoop2.jar:/usr/lib/avro/avro-tools.jar
$ javac -classpath $CLASSPATH com/miguno/avro/twitter_schema.java
$ jar cvf Twitter.jar com/miguno/avro/*.class
在當前目錄,運行 spark-shell:
spark-shell --jars /usr/lib/avro/avro-mapred-hadoop2.jar,/usr/lib/avro/avro.jar,Twitter.jar
將 twitter.avro 上傳到 hdfs:
hadoop fs -put twitter.avro
在 REPL 中,我們創建一個 RDD 並查看結果是否和上面一致:
scala>
import com.miguno.avro.twitter_schema;
import org.apache.avro.file.DataFileReader;
import org.apache.avro.file.DataFileWriter;
import org.apache.avro.io.DatumReader;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.specific.SpecificDatumReader;
import org.apache.avro.mapreduce.AvroKeyInputFormat
import org.apache.avro.mapred.AvroKey
import org.apache.hadoop.io.NullWritable
import org.apache.avro.mapred.AvroInputFormat
import org.apache.avro.mapred.AvroWrapper
import org.apache.avro.generic.GenericRecord
import org.apache.avro.mapred.{AvroInputFormat, AvroWrapper}
import org.apache.hadoop.io.NullWritable
val path = "twitter.avro"
val avroRDD = sc.hadoopFile[AvroWrapper[GenericRecord], NullWritable, AvroInputFormat[GenericRecord]](path)
avroRDD.map(l => new String(l._1.datum.get("username").toString() ) ).first
更多的 Avro Tools 用法,可以參考 Avro 介紹。