mappartitions java_Java Spark系列——关于 mapPartitions的误区 _好机友

前言

今天 Review 了一下同事的代码, 发现其代码中有非常多的 mapPartitions, 问其原因,他说性能比 map 更好。 我说为什么性能好呢? 于是就有了这篇文章

网上推崇 mapPartitions 的原因执行次数变少,速度更快 按照某些文章的原话来说 一次函数调用会处理一个partition所有的数据,而不是一次函数调用处理一条,性能相对来说会高一些。 我想说的是: 一次函数调用会处理一个partition所有的数据, 确实是可以节省你调用函数的那微乎其微的时间开销, 但是这个节省的时间真的太小了, 尤其是对与spark这种框架, 本身就不是用来做毫秒级响应的东西, 甚至硬要扯的话,你引入迭代器, 做迭代器的操作难道就不要消耗时间的么? 如果说上面这种说法还有那么一丢丢靠谱的话, 有些说法就真的让我很无语了, 比如说: 如果是普通的map,比如一个partition中有1万条数据;ok, 那么你的function要执行和计算1万次。 但是,使用MapPartitions操作之后,一个task仅仅会执行一次function, function一次接收所有的partition数据。只要执行一次就可以了,性能比较高 这种说法如果按照上面的方式来理解其实也是那么一回事, 但是也很容易让一些新人理解为: map要执行1万次,而 MapPartitions 只需要一次,这速度杠杠的提升了啊 实际上,你使用MapPartitions迭代的时候,还是是一条条数据处理的,这个次数其实完全没变。

mapPartitions 带来的问题

其实就我个人经验来看, mapPartitions 的正确使用其实并不会造成什么大的问题, 当然我也没看出普通场景 mapPartitions 比 map 有什么优势, 所以 完全没必要刻意使用 mapPartitions 反而,mapPartitions 会带来一些问题。使用起来并不是很方便,这个写过代码的人应该都知道。 当然这个问题并不是不能解决,我们可以写类似下面的代码, 确实也变的和 map 简洁性也差不太多, 恩,我不会告诉你可以尝试在生产环境中用用噢。 //抽象出一个函数,以后所有的 mapPartitions 都可以用 def mapFunc[T, U](iterator: Iterator[T], f: T => U) = { iterator.map(x => { f(x) }) } //使用 rdd.mapPartitions(x => { mapFunc(x, line => { s"${line}转换数据" }) })

容易造成 OOM,这个也是很多博客提到的问题, 他们大致会写出如下的代码来做测试, rdd.mapPartitions(x => { xxxx操作 while (x.hasNext){ val next = x.next() } xxx操作 }) 如果你的代码是上面那样,那OOM也就不足为奇了, 不知道你注意到了没有,mapPartitions 是接受一个迭代器, 再返回一个迭代器的, 如果你这么写代码,就完全没有使用到迭代器的懒执行特性。 将数据都堆积到了内存, 真就变成了一次处理一个partition的数据了, 在某种程度上已经破坏了 Spark Pipeline 的计算模式了。

mapPartitions 到底该怎么用

存在即是道理, 虽然上面一直在吐槽, 但是其确实有存在的理由。 其一个分区只会被调用一次的特性, 在一些写数据库的时候确实很有帮助, 因为我们的 Spark 是分布式执行的, 所以连接数据库的操作必须放到算子内部才能正确的被Executor执行, 那么 mapPartitions 就显示比 map 要有优势的多了。 比如下面这段伪代码rdd.mapPartitions(x => { println("连接数据库") val res = x.map(line=>{ print("写入数据:" + line) line }) println("断开数据库") res })

这样我就一个分区只要连接一次数据库, 而如果是 map 算子,那可能就要连接 n 多次了。

另外一点就是 mapPartitions 提供给了我们更加强大的数据控制力, 怎么理解呢?我们可以一次拿到一个分区的数据, 那么我们就可以对一个分区的数据进行统一处理, 虽然会加大内存的开销,但是在某些场景下还是很有用的, 比如一些矩阵的乘法。

后记

不管你要使用哪个算子,其实都是可以的, 但是大多数时候,我还是推荐你使用 map 算子, 当然遇到一些map算子不合适的场景, 那就没办法了... 不过就算你是真的要使用 mapPartitions, 那么请记得充分发挥一下 迭代器的 懒执行特性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
影评案例是一个很好的Spark练习案例,可以帮助你熟悉Spark的基本操作和数据处理。下面我为你提供一个简单的影评案例,希望能帮助到你。 1. 数据准备 我们需要一份电影评分数据集,可以从MovieLens网站上下载到,这里我们使用ml-latest-small数据集,该数据集包含了用户对电影的评分、电影信息和用户信息等数据。 2. 数据处理 我们需要将数据导入到Spark中,并进行数据预处理。首先,我们需要将数据转换成DataFrame类型,然后对数据进行清洗和整理,例如去掉重复数据、缺失数据处理等。 以下是一个简单的处理代码: ``` from pyspark.sql.functions import col # 读取数据 ratings = spark.read.csv("ratings.csv", header=True, inferSchema=True) # 去重 ratings = ratings.dropDuplicates() # 处理缺失值 ratings = ratings.dropna() # 转换数据类型 ratings = ratings.withColumn("userId", col("userId").cast("int")) ratings = ratings.withColumn("movieId", col("movieId").cast("int")) ratings = ratings.withColumn("rating", col("rating").cast("double")) # 查看数据 ratings.show() ``` 3. 数据分析 我们可以使用Spark进行各种数据分析操作,例如对电影评分进行统计,找出评分最高的电影,计算每个用户的平均评分等。 以下是一个简单的分析代码: ``` from pyspark.sql.functions import desc, avg # 统计每个电影的评分数量和平均评分 movie_ratings = ratings.groupBy("movieId").agg({"rating": "count", "rating": "avg"}) movie_ratings = movie_ratings.withColumnRenamed("count(rating)", "num_ratings").withColumnRenamed("avg(rating)", "avg_rating") # 找出评分最高的电影 top_movies = movie_ratings.orderBy(desc("avg_rating")).limit(10) # 计算每个用户的平均评分 user_ratings = ratings.groupBy("userId").agg(avg("rating")) # 查看结果 top_movies.show() user_ratings.show() ``` 4. 结果展示 最后,我们可以将结果保存到文件或数据库中,或者使用可视化工具展示结果。 以上是一个简单的Spark影评案例,希望能够帮助到你。如果你想深入学习Spark,可以尝试更复杂的案例和练习。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值