文章目录
引言
大多数系统中,采集的原始日志数据往往都是半结构化或者非结构化的。而对于数据分析人员来说,每次写代码去处理原始数据不够高效且有一定的难度,二维表结构的数据使用起来才是最自然方便的。因此,数据处理的第一步往往是将原始数据展开成一张底层大宽表以供上层的数据应用或者数据开发人员使用。但对于原始日志数据中字段往往很多,而且多是嵌套结构的,对于底层大宽表应该包含哪些字段,以及对于嵌套结构如何处理,这是一个令人头疼的问题。
那能直接使用SQL方法分析处理原始数据吗?
一方面,Spark计算框架可以支持大数据量的计算,而且其支持SQL接口;另一方面,Protobuf是一种常用的数据接口协议。本文将protobuf协议为例介绍使用Spark SQL分析处理原始数据的方案。
对于上层报表类应用来说,我们可以基于报表需求选取字段形成大宽表。但对于分析型应用,特别对于一些问题诊断分析场景来说,往往需要一些不常使用的字段,且往往需要最原始未经加工的数据。
对于其它嵌套结构数据,如json,只需要将RDD转换DataFrame步骤进行替换即可达到同样的目的。
将原始数据转换为DataFrame
原始数据是protobuf协议的数据,如果能将protobuf格式的日志数据转换成DataFrame,那就可以使用Spark SQL来进行数据分析计算了。
定义数据格式
假设protobuf协议如下:
package test;
enum Sex {
Female = 1;
Male = 2;
}
message Job {
required string company = 1;
repeated string industries = 2;
}
message Person {
required string name = 1;
required Sex sex = 2;
required int32 age = 3;
repeated string interests = 4;
repeated Job jobs = 5;
}
message Log {
repeated Person person = 1;
}
读取数据为RDD
假设日志数据已经采集好并存储在hdfs或其他存储系统中,那么首先可通过相应读数据方法加载数据并转换为RDD[Protobuf]格式。下面以本地的测试数据person.txt
为例进行介绍:
person {
name: "Li"
sex: Male
age: 27
interests: "badminton"
interests: "running"
interests: "reading"
jobs {
company: "Google"
industries: "Internet"
}
}
person {
name: "Zhang"
sex: Male
age: 26
interests: "basketball"
interests: "running"
interests: "reading"
jobs {
company: "Tencent"
industries: "Internet"
industries: "Game"
}
}
person {
name: "Wang"
sex: Female
age: 26
interests: "dancing"
interests: "swim"
interests: "reading"
jobs {
company: "Alibaba"
industries: "Internet"
industries: "E-commerce"
}
}
person {
name: "Liu"
sex: Female
age: 23
interests: "song"
interests: "swim"
interests: "game"
jobs {
company: "Tencent"
industries: "Internet"
industries: "Game"
}
}
读取本地测试数据,并将其转换为RDD[Person]:
val log = {
val in = getClass.getResourceAsStream("/person.txt")
val builder = Log.newBuilder()
TextFormat.merge(new BufferedReader(new InputStreamReader(in)), builder)
builder.build()
}
val personRdd = sc.parallelize(log.getPersonList)
将RDD[Protobuf]转换为DataFrame
如果是RDD[JavaBean]格式的数据,可以利用SparkSession中createDataFrame
方法将其转换为DataFrame。那对于Protobuf格式的数据也可通过自动解析Protobuf协议的方式进行转换,可参考saurfang的sparksql-protobuf包(也可以参考其源码编写自己的转换方式)将RDD[Protobuf]转换为DataFrame。其依赖maven包如下:
<!-- https://mvnrepository.com/artifact/com.github.saurfang/sparksql-protobuf -->
<dependency>
<groupId>com.github.saurfang</groupId>
<artifactId>sparksql-protobuf_2.11</artifactId>
<version>0.1.3</version>
</dependency>
将RDD[Person]转换为DataFrame的schema如下:
import com.github.saurfang.parquet.proto.spark.sql._
val personsDF = sparkSession.sqlContext.createDataFrame(personRdd)
personsDF.printSchema()
root
|-- name: string (nullable = false)
|-- sex: string (nullable = false)
|-- age: integer (nullable = false)
|-- interests: array (nullable = false)
| |-- element: string (containsNull = false)
|-- jobs: array (nullable = false)
| |-- element: struct (containsNull = false)
| | |-- company: string (nullable = false)
| | |-- industries: array (nullable = false)
| | | |-- element: string (containsNull = false)
转换为DataFrame结构的数据后,就可以使用Spark SQL对其进行分析处理。比如可统计不同性别下的人数:
personsDF.createOrReplaceTempView("person")
val result =
sparkSession.sql("select sex, count(*) as cnt from person group by sex")
result.show()
+------+---+
| sex|cnt|
+------+---+
|Female| 2|
| Male| 2|
+------+---+
字段裁剪优化
在将RDD[Protobuf]转换为DataFrame时,数据占用的内存可能会膨胀很多