<Spark快速大数据分析>读书笔记(二)

PART 3 Pair RDD

  Spark为包含键值对类型的RDD提供了专有操作,这类RDD叫做Pair RDD(意为“对RDD”)

  Spark中Pair RDD的创建主要有两种方式,一种方式从存储了键值对数据的文件中创建(主要内容见PART 5),另一种方式可以从其他普通RDD调用map()操作来实现:

1 #line的元素是一行单词构成的句子,map操作后每个元素为该句子的首个单词和句子本身构成一个二元元组
2 pairs = line.map(lambda x: (x.split(" ")[0], x))

      Spark中单个Pair RDD的转化操作有:

  Spark中针对两个Pair RDD的转化操作有:

  从数据处理的角度来说,以上操作不外乎四种:

  • 聚合
  • 分组
  • 连接
  • 排序

  聚合操作:当数据以键值对形式组织的时候,将具有相同键的元素进行统计操作。combineByKey()是最为常用的基于键的聚合函数。其用法如下:

1 #Python中使用combineByKey()对每个键求平均值
2 sumCount = rdd.combineByKey((lambda x:(x, 1)),  
3                             (lambda x, y: (x[0] + y, x[1] + 1)),
4                             (lambda x, y: (x[0] + y[0], x[1] + x[1]))) 

  其中lambda x: (x, 1):createCombiner()函数来创造键对应的累加器的初始值{(x, 1)},这一个过程会在每个分区第一次出现这个键时调用,而不是仅在整个RDD中第一次出现时调用。

  lambda x, y: (x[0] + y, x[1] + 1):mergeValue()函数将累加器的当前值(x)与最新值(y)进行合并

  lambda x, y: (x[0] + y[0], x[1] + x[2]):mergeCombiners()函数将各个分区的累加器合并(x和y为各个分区的累加器)

  分组操作:将数据根据键进行分组,类似MySQL里的GROUP BY。主要操作有groupByKey(),groupBy()和cogroup()。groupBy()操作接受一个函数,对源RDD中的每个元素作用该函数后,将函数结果作为键进行分组。cogroup()不仅可以用于实现两个及以上的RDD的分组和连接操作,还可用于求交集(合并为补分值为空,则表示不属于交集?)

  连接操作:连接方式主要有:右外连接,左外连接,内连接,交叉连接,概念与MySQL中一致,具体函数如上图中join(), rightOuterJoin(), leftOuterJoin()等

  排序操作

1 #在Python中以字符串顺序对整数进行自定义排序
2 rdd.sortByKey(ascending=True, numPartitions=None, keyfunc = lambda x:str(x))

  ascending表示是否按照升序排序,默认为true, keyfunc表示自定义的比较函数,将该结果作为排序的依据。

  并行度调优:每个RDD都有固定数目的分区,分区数决定了RDD上执行操作的并行度。Spark始终会尝试根据集群的规模推断出一个有意义的默认值,但是有时候需要通过对并行度进行调整来获取更好的性能表现(什么情况下调?调成多少?怎么调?)

  怎么调:

1 #在Python中自定义reduceByKey()的并行度
2 data = [("a", 3), ("b", 2), ("a", 1)]
3 sc.parallelize(data).reduceByKey(lambda x, y: x + y)#默认并行度
4 sc.parallelize(data).reduceByKey(lambda x, y: x + y, 10)#自定义并行度

  有时,若希望在分组与聚合操作以外的操作中也能改变RDD的分区,则可以使用repartition()函数,它会把数据通过网络进行混洗,并创造出新的分区集合。Python中使用rdd.getNumPartitions查看RDD的分区

  

  

PART 4 数据分区

  在分布式程序中,通信的代价是很大的,控制数据分布以获得最少的网络传输可以极大地提升整体性能。

  Spark程序可以通过控制RDD分区方式来减少通信开销,但是分区并不是对所有应用都有好处,比如给定RDD只需要被扫描一次就完全没有必要对其进行分区处理。只有当数据集多次在诸如连接这种基于键的操作中使用时,分区才会有所帮助。

  Spark中所有键值对RDD都可以进行分区。Spark可以确保同一组的键出现在同一个节点(分区与节点的关系是?)上。

  默认情况下,连接操作会对两个RDD的键的哈希值都计算出来,然后将哈希值结果相同的记录通过网络传输到同一台机器上,然后在那台机器上对所有键相同的记录进行连接操作。

  假设当前有一个很大的用户信息表,存储了大量的用户信息,是一个由(UserID, UserInfo)对组成的RDD,该文件会周期性得与一个小文件进行组合,这个小文件存放着过去5分钟内某网站各用户的访问情况,由(UserID, LinkInfo)对组成的表。我们可能需要统计用户对于其未关注的主题的浏览情况,这时可以使用Spark的join()操作来实现这个组合操作。但是userData表比events表要大得多,而且userData数据常常变化较小,每次调用都对userData表进行哈希值计算和跨界点数据混洗,则会浪费很多时间。

  解决的方案是,在程序开始时就对userData表使用partitionBy()转化操作,将这张表转为哈希分区(Python中只需要把需要的分区数传递过去,Java和Scala则可以通过传递一个spark.HashPartitioner对象),特别需要注意的是,需要对partitionBy()转化操作的结果进行持久化,否则后面每次用到这个RDD时都会重复地对数据进行分区操作。

  许多Spark操作为自动为结果RDD设定已知的分区方式信息,例如sortByKey()会自动生成范围分区的RDD,groupByKey()会生成哈希分区的RDD。

  许多操作会利用到已有的分区信息,如join操作。

  还有部分操作会导致新的RDD失去父RDD的分区信息。例如map()操作,因为这样的操作理论上讲可能改变原有记录的键

  1.获取分区方式:

  Java和Scala可以通过rdd.partitioner获得分区方式

  2.从分区中获益的操作:

  对于像cogroup()和join()操作这样的二元操作,预先进行数据分区,可以导致其中至少一个RDD不发生数据混洗。

  3.会为生成的结果RDD设好分区方式的操作:

  cogroup(),groupWith(),join(),leftOuterJoin(),rightOuterJoin(),groupByKey(),reduceByKey(),combineByKey(),partitionBy(),sort()

  另外,如果父RDD有分区方式的话,mapValues(),flatMapValues()以及filter()也会为子RDD设定好分区方式。

  其他所有操作生成的结果都不会存在特定的分区方式。

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

  4.自定义分区方式:

1 import urlparse
2 def hash_domain(url):
3     return hash(urlparse.urlparse(url).netloc)
4 
5 rdd.partitionBy(20, hash_domain) #创建20个分区

 

  

posted @ 2018-01-31 22:37  McConor  阅读(139)  评论(0)    收藏  举报