spark键值对操作(二)之数据分区

来源:互联网 发布:mac口红是什么牌子 编辑:程序博客网 时间:2024/05/16 19:15

1.数据分区

为了减小分布式应用通信的代价,所以要控制数据分区以获得最小的网络传输
spark中所有键值对RDD都可以进行分区

有如下这样的需求:需要对用户访问其未订阅页面
的情况进行统计,以更好的向用户推荐内容。现有一个很大的用户信息表(UserID,UserInfo)对组成的RDD,其中UserInfo包含一个该用户所订阅的主题列表,该应用会周期性与一个小文件(UserID,LinkInfo)组合,这个小文件存放着过去五分钟内网站各用户的访问情况

//scala// 初始化代码;从HDFS商的一个Hadoop SequenceFile中读取用户信息// userData中的元素会根据它们被读取时的来源,即HDFS块所在的节点来分布// Spark此时无法获知某个特定的UserID对应的记录位于哪个节点上val sc = new SparkContext(...)val userData = sc.sequenceFile[UserID, UserInfo]("hdfs://...").persist()// 周期性调用函数来处理过去五分钟产生的事件日志// 假设这是一个包含(UserID, LinkInfo)对的SequenceFiledef processNewLogs(logFileName: String) {    val events = sc.sequenceFile[UserID, LinkInfo](logFileName)    val joined = userData.join(events)// RDD of (UserID, (UserInfo, LinkInfo)) pairs    val offTopicVisits = joined.filter {        case (userId, (userInfo, linkInfo)) => !userInfo.topics.contains(linkInfo.topic)    }.count()    println("Number of visits to non-subscribed topics: " + offTopicVisits)}

代码存在的问题:
在每次调用processNewLogs()时,会进行如下的操作,首先将两个数据集的所有键的hash值计算出,然后将hash相同的记录通过网络传到同一台机器上,然后对所有键相同的记录进行连接操作。因为userdata RDD相当大,并且及时userdataRDD不变,但是仍然需要对userDataRDD进行hash值计算和shuffle。

解决方案
对userdata 表使用partitionBy()将该表转化为哈希分区

// 构造100个分区val userData = sc.sequenceFile[UserID, UserInfo]("hdfs://...").partitionBy(new HashPartitioner(100)) .persist()

spark其他一些操作会自动为结果RDD设定已知的分区方式信息,除join外还有很多操作会利用已有的分区信息,例如sortByKey() 和groupByKey(),map() 这样的操作会导致新的RDD 失去父RDD 的分区信息

1.1获取RDD的分区方式

val pairs = sc.parallelize(List((1, 1), (2, 2), (3, 3)))pairs.partitionerval partitioned = pairs.partitionBy(new spark.HashPartitioner(2))partitioned.partitioner

如果在后续中使用partitioned,那么就要进行persist()操作,否则后续的RDD操作会对partitioned的整个谱系重新求值

1.2从分区中获益的操作

对数据跨节点shuffle的过程都是数据分区中获益的过程
cogroup()groupWith()join()leftOuterJoin()rightOuterJoin()groupByKey()reduceByKey()combineByKey()lookup()

reduceByKey因为在本地上进行规约操作结束后才从个节点传到主节点,所以网络开销不大,对于cogroup()和join这样的二元操作,预先进行数据分区会导致至少其中一个RDD不发生shuffle

1.3影响分区方式的操作

spark内部的操作知道如何影响分区方式,并将对数据进行的操作的结果RDD自动设置对应的分区器,如果调用join来连接两个RDD,由于键相同的元素会被哈希到同一台机器上,spark知道输出的结果也是hash分区的,这样对结果进行reduceByKey()这样的操作会快很多

但是map因为键可能会被改变,所以不会有固定的分区方式,不过mapValues() 和flatMapValues() 可以作为替代方法

以下是为生成的结果RDD设置好分区方式的操作
cogroup()、groupWith()、join()、lef tOuterJoin()、rightOuterJoin()、groupByKey()、reduceByKey()、combineByKey()、partitionBy()、sort()、mapValues()

对于二元操作,输出数据的分区方式取决于父RDD 的分区方式。默认情况下,结果会采用哈希分区,分区的数量和操作的并行度一样。不过,如果其中的一个父RDD 已经设置过分区方式,那么结果就会采用那种分区方式;如果两个父RDD 都设置过分区方式,结果RDD 会采用第一个父RDD 的分区方式。

1.3自定义分区方式

http://www.cnn.com/WORLD和http://www.cnn.com/US进行hash分区时,会被分配到不同的节点,但是为了避免shuffle,我们需要自定义分区方式

import urlparsedef hash_domain(url):    return hash(urlparse.urlparse(url).netloc)rdd.partitionBy(20, hash_domain) # 创建20个分区