决定做一个非常无聊的实验,众所周知现在使用spark进行数据分析一般采用rdd分布式编程、dataframe接口和使用spark sql执行的方式,那么在忽略数据加载速度的情况下,究竟哪种方式的运行速度最快呢?
至于rdd和dataframe数据集的原理和区别,我就不在这里介绍了,可以看RDD DataFrame Dataset 三者的优缺点 , 三者之间的创建 , 以及相互转换这篇文章。
数据集介绍
本文使用的数据集是某省市某网约车公司的交通出行大数据集,由于发布方要求,笔者不会传播该数据集。感兴趣的同学可以前往海口市-交通流量时空演变特征可视分析自行查看和下载数据集。
该数据集包含某平台2017年5月1日到10月31日在某市每天的订单数据,包含订单的起终点经纬度以及订单的类型、出行品类、乘车人数等订单属性数据。
数据加载
首先加载本地数据文件,由于我们是测试,当然是采用local-thread模式运行spark应用。
val path = "data/dwv_order_make_1.txt" // Current fold file
val textRdd = sc.textFile(path,2)
通过schema,Row构造dataframe
val structFields = Array(StructField("order_id", StringType, nullable = false), // 0
StructField("product_id", IntegerType, nullable = true),// 1
StructField("city_id", StringType, nullable = true),// 2
StructField("district", StringType, nullable = true),// 3
StructField("county", StringType, nullable = true),// 4
StructField("type", IntegerType, nullable = true),// 5
StructField("combo_type", IntegerType, nullable = true),// 6
StructField("traffic_type", IntegerType, nullable = true),// 7
StructField("passenger_count", IntegerType, nullable = true),// 8
StructField("driver_product_id", StringType, nullable = true),// 9
StructField("start_dest_distance", FloatType, nullable = true),// 10
StructField("arrive_time", StringType, nullable = true),// 11
StructField("departure_time", StringType, nullable = true),// 12
StructField("others", StringType, nullable = true)// 13
)
val structType = StructType(structFields) // create schema struct
val orderRdd = textRdd.map(_.split("\t")).map(x=>Row(x(0), x(1).toInt, x(2), x(3), x(4), x(5).toInt,
x(6).toInt, x(7).toInt, x(8).toInt, x(9), x(10).toFloat, x(11), x(12), x(13)))
val df = spark.createDataFrame(orderRdd, structType)
df.show
这段代码的运行结果如下:
+--------------+----------+-------+--------+------+----+----------+------------+---------------+-----------------+-------------------+-------------------+-------------------+------+
| order_id|product_id|city_id|district|county|type|combo_type|traffic_type|passenger_count|driver_product_id|start_dest_distance| arrive_time| departure_time|others|
+--------------+----------+-------+--------+------+----+----------+------------+---------------+-----------------+-------------------+-------------------+-------------------+------+
|17592725309572| 3| 83| 0898|460107| 0| 0| 0| 0| 3| 5770.0|2017-05-19 11:04:02|2017-05-19 11:01:35| 16|
|17592725855561| 3| 83| 0898|460106| 0| 0| 0| 0| 3| 3839.0|2017-05-19 11:35:04|2017-05-19 11:31:29| 11|
|17592726010947| 3| 83| 0898|460106| 0| 0| 0| 0| 3| 6780.0|2017-05-19 11:51:08|2017-05-19 11:47:13| 17|
+--------------+----------+-------+--------+------+----+----------+------------+---------------+-----------------+-------------------+-------------------+-------------------+------+
spark sql 运行速度测试
我选择了几种常用的sql语句以提高计算复杂度,更贴合业务情况。
我们的sql执行效果是选取某两分钟内,城市编号为’460107’的拼车(或无订单状态)并按照初始上车距离进行排序的订单号。
编写sql语句如下:
SELECT orders.order_id FROM orders WHERE orders.departure_time >= '2017-05-19 17:41:00' AND
orders.departure_time <= '2017-05-19 17:43:00' AND orders.county = '460107' AND orders.traffic_type = 0 OR
orders.traffic_type = 4
ORDER BY orders.start_dest_distance
运行结果如下(只保留前五条):
+--------------+
| order_id|
+--------------+
|17592733902055|
|17592733986620|
|17592733839582|
|17592734004880|
|17592733964943|
使用spark sql 解释并执行该sql语句,共用时9610
毫秒。
dataframe 运行速度测试
Spark SQL中的DataFrame类似于一张关系型数据表。在关系型数据库中对单表或进行的查询操作,在DataFrame中都可以通过调用其API接口来实现。
调用代码如下:
df.where("departure_time >='2017-05-19 17:41:00'")
.where("departure_time <= '2017-05-19 17:43:00'")
.where("county = '460107'")
.where("traffic_type = 0 OR traffic_type = 4")
.orderBy("start_dest_distance")
.select("order_id")
.show(1000)
运行结果如下(只保留前五条):
+--------------+
| order_id|
+--------------+
|17592733902055|
|17592733986620|
|17592733839582|
|17592734004880|
|17592733964943|
可以看到运行结果与spark sql是一致的,使用dataframe提供的api接口共用时8771
毫秒。
RDD运行速度测试
下面是一段我自己写的RDD Api编程实现,通过构建一个元组类型的RDD在上面进行过滤、排序等操作。
val tRDD = textRdd.map(_.split("\t"))
val f1RDD = tRDD.map(x => (x(12), x(4), x(7).toInt, x(10).toFloat, x(0)))
val rRDD = f1RDD.filter(_._1 >= "2017-05-19 17:41:00").filter(_._1 <= "2017-05-19 17:43:00")
.filter(_._2 == "460107").filter(x => x._3 == 4 || x._3 == 0).sortBy(_._4).map(_._5)
rRDD.collect().foreach {println}
运行结果如下(只保留前五条),可以看出运行结果与前面两种是一致的:
17592733902055
17592733986620
17592733839582
17592734004880
17592733964943
这段代码运行用时共8925
毫秒。
总结
引用RDD、DataFrame和DataSet的区别中的一句话:
RDD API是函数式的,强调不变性,在大部分场景下倾向于创建新对象而不是修改老对象。这一特点虽然带来了干净整洁的API,却也使得Spark应用程序在运行期倾向于创建大量临时对象,对GC造成压力。在现有RDD API的基础之上,我们固然可以利用mapPartitions方法来重载RDD单个分片内的数据创建方式,用复用可变对象的方式来减小对象分配和GC的开销,但这牺牲了代码的可读性,而且要求开发者对Spark运行时机制有一定的了解,门槛较高。另一方面,Spark SQL在框架内部已经在各种可能的情况下尽量重用对象,这样做虽然在内部会打破了不变性,但在将数据返回给用户时,还会重新转为不可变数据。利用 DataFrame API进行开发,可以免费地享受到这些优化效果。
同时从代码和测试结果中可以看出,用spark.sql比较方便快捷,而使用dataframe的api相较而言有些许速度提升,至于自己去进行rdd api编程如果不是为了解决功能上的问题,并没有例如速度或可维护性方面的优势。在日常(dataframe api 可满足的)应用中我更倾向于通过dataframe api接口进行spark应用开发。