spark点滴之map-flatMap~mappartition~sortBY~fold~combinebykey~分区~mvn~scala

map 和 flatMap:

通过一个实验来看Spark 中 map 与 flatMap 的区别。


步骤一:将测试数据放到hdfs上面

hadoopdfs -put data1/test1.txt /tmp/test1.txt

该测试数据有两行文本:

line one

line two


步骤二:在Spark中创建一个RDD来读取hdfs文件/tmp/test1.txt

val textFile = sc.textFile("/tmp/test1.txt")


步骤三:查看map函数的返回值

得到map函数返回的RDD: 

val mapres = textFile.map(_,split("\\s+"))

查看map函数的返回值——文件中的每一行数据返回了一个数组对象

mapres.collect  ====>>

Array(Array(line,one),Array(line,two))

步骤四:查看flatMap函数的返回值

得到flatMap函数返回的RDD:

val flatRes = textFile.flatMap(_.split("\\s+"))

查看flatMap函数的返回值——文件中的所有行数据仅返回了一个数组对象

Array(line,one,line,two)


总结:

- Spark 中 map函数会对每一条输入进行指定的操作,然后为每一条输入返回一个对象;

- 而flatMap函数则是两个操作的集合——正是“先映射后扁平化”:

   操作1:同map函数一样:对每一条输入进行指定的操作,然后为每一条输入返回一个对象

   操作2:最后将所有对象合并为一个对象


mapPartitions

def mapPartitions[U](f: (Iterator[T]) => Iterator[U], preservesPartitioning: Boolean = false)(implicit arg0: ClassTag[U]): RDD[U]

该函数和map函数类似,只不过映射函数的参数由RDD中的每一个元素变成了RDD中每一个分区的迭代器。如果在映射的过程中需要频繁创建额外的对象,使用mapPartitions要比map高效的过。

比如,将RDD中的所有数据通过JDBC连接写入数据库,如果使用map函数,可能要为每一个元素都创建一个connection,这样开销很大,如果使用mapPartitions,那么只需要针对每一个分区建立一个connection。

参数preservesPartitioning表示是否保留父RDD的partitioner分区信息。

 

 
 
  1. var rdd1 = sc.makeRDD(1 to 5,2)
  2. //rdd1有两个分区
  3. scala> var rdd3 = rdd1.mapPartitions{ x => {
  4. | var result = List[Int]()
  5. | var i = 0
  6. | while(x.hasNext){
  7. | i += x.next()
  8. | }
  9. | result.::(i).iterator
  10. | }}
  11. rdd3: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[84] at mapPartitions at :23
  12.  
  13. //rdd3将rdd1中每个分区中的数值累加
  14. scala> rdd3.collect
  15. res65: Array[Int] = Array(3, 12)
  16. scala> rdd3.partitions.size
  17. res66: Int = 2
  18.  

mapPartitionsWithIndex

def mapPartitionsWithIndex[U](f: (Int, Iterator[T]) => Iterator[U], preservesPartitioning: Boolean = false)(implicit arg0: ClassTag[U]): RDD[U]

函数作用同mapPartitions,不过提供了两个参数,第一个参数为分区的索引。

 
 
  1. var rdd1 = sc.makeRDD(1 to 5,2)
  2. //rdd1有两个分区
  3. var rdd2 = rdd1.mapPartitionsWithIndex{
  4. (x,iter) => {
  5. var result = List[String]()
  6. var i = 0
  7. while(iter.hasNext){
  8. i += iter.next()
  9. }
  10. result.::(x + "|" + i).iterator
  11. }
  12. }
  13. //rdd2将rdd1中每个分区的数字累加,并在每个分区的累加结果前面加了分区索引
  14. scala> rdd2.collect
  15. res13: Array[String] = Array(0|3, 1|12)
  16.  


mapPartitions(function) 
map()的输入函数是应用于RDD中每个元素,而mapPartitions()的输入函数是应用于每个分区

<code class="hljs scala has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">package</span> test

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> scala.Iterator

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> org.apache.spark.SparkConf
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> org.apache.spark.SparkContext

<span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">object</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">TestRdd</span> {</span>
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> sumOfEveryPartition(input: Iterator[Int]): Int = {
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">var</span> total = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>
    input.foreach { elem =>
      total += elem
    }
    total
  }
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> main(args: Array[String]) {
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">val</span> conf = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> SparkConf().setAppName(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Spark Rdd Test"</span>)
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">val</span> spark = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> SparkContext(conf)
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">val</span> input = spark.parallelize(List(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">4</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">5</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">6</span>), <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//RDD有6个元素,分成2个partition</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">val</span> result = input.mapPartitions(
      partition => Iterator(sumOfEveryPartition(partition)))<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//partition是传入的参数,是个list,要求返回也是list,即Iterator(sumOfEveryPartition(partition))</span>
    result.collect().foreach {
      println(_)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//6 15</span>
    }
    spark.stop()
  }
}</code>

sortBy和sortByKey函数详解

在很多应用场景都需要对结果数据进行排序,Spark中有时也不例外。在Spark中存在两种对RDD进行排序的函数,分别是 sortBy和sortByKey函数。sortBy是对标准的RDD进行排序,它是从Spark 0.9.0之后才引入的(可以参见SPARK-1063)。而sortByKey函数是对PairRDD进行排序,也就是有Key和Value的RDD。下面将分别对这两个函数的实现以及使用进行说明。
  

一、sortBy函数实现以及使用

  sortBy函数是在org.apache.spark.rdd.RDD类中实现的,它的实现如下:

01 /**
02  * Return this RDD sorted by the given key function.
03  */
04 def sortBy[K](
05     f: (T) => K,
06     ascending: Boolean = true,
07     numPartitions: Int = this.partitions.size)
08     (implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T] =
09   this.keyBy[K](f)
10       .sortByKey(ascending, numPartitions)
11       .values

  该函数最多可以传三个参数:
  第一个参数是一个函数,该函数的也有一个带T泛型的参数,返回类型和RDD中元素的类型是一致的;
  第二个参数是ascending,从字面的意思大家应该可以猜到,是的,这参数决定排序后RDD中的元素是升序还是降序,默认是true,也就是升序;
  第三个参数是numPartitions,该参数决定排序后的RDD的分区个数,默认排序后的分区个数和排序之前的个数相等,即为this.partitions.size
  从sortBy函数的实现可以看出,第一个参数是必须传入的,而后面的两个参数可以不传入。而且sortBy函数函数的实现依赖于sortByKey函数,关于sortByKey函数后面会进行说明。keyBy函数也是RDD类中进行实现的,它的主要作用就是将将传进来的每个元素作用于f(x)中,并返回tuples类型的元素,也就变成了Key-Value类型的RDD了,它的实现如下:

1 /**
2 * Creates tuples of the elements in this RDD by applying `f`.
3 */
4 def keyBy[K](f: => K): RDD[(K, T)] = {
5     map(x => (f(x), x))
6 }

  那么,如何使用sortBy函数呢?

01 /**
02  * User: 过往记忆
03  * Date: 14-12-26
04  * Time: 上午10:16
05  * bolg: http://www.iteblog.com
06  * 本文地址:http://www.iteblog.com/archives/1240
07  * 过往记忆博客,专注于hadoop、hive、spark、shark、flume的技术博客,大量的干货
08  * 过往记忆博客微信公共帐号:iteblog_hadoop
09  */
10 scala> val data = List(3,1,90,3,5,12)
11 data: List[Int] = List(31903512)
12  
13 scala> val rdd = sc.parallelize(data)
14 rdd: org.apache.spark.rdd.RDD[Int] =ParallelCollectionRDD[0] at parallelize at <console>:14
15  
16 scala> rdd.collect
17 res0: Array[Int] = Array(31903512)
18  
19 scala> rdd.sortBy(x => x).collect
20 res1: Array[Int] = Array(13351290)
21  
22 scala> rdd.sortBy(x => x, false).collect
23 res3: Array[Int] = Array(90125331)
24  
25 scala> val result = rdd.sortBy(x => x, false)
26 result: org.apache.spark.rdd.RDD[Int] = MappedRDD[23] at sortBy at <console>:16
27  
28 scala> result.partitions.size
29 res9: Int = 2
30  
31 scala> val result = rdd.sortBy(x => x, false1)
32 result: org.apache.spark.rdd.RDD[Int] = MappedRDD[26] at sortBy at <console>:16
33  
34 scala> result.partitions.size
35 res10: Int = 1

  上面的实例对rdd中的元素进行升序排序。并对排序后的RDD的分区个数进行了修改,上面的result就是排序后的RDD,默认的分区个数是2,而我们对它进行了修改,所以最后变成了1。
  

二、sortByKey函数实现以及使用

  sortByKey函数作用于Key-Value形式的RDD,并对Key进行排序。它是在org.apache.spark.rdd.OrderedRDDFunctions中实现的,实现如下

1 def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.size)
2     : RDD[(K, V)] =
3 {
4   val part = new RangePartitioner(numPartitions, self, ascending)
5   new ShuffledRDD[K, V, V](self, part)
6     .setKeyOrdering(if (ascending) ordering else ordering.reverse)
7 }

  从函数的实现可以看出,它主要接受两个函数,含义和sortBy一样,这里就不进行解释了。该函数返回的RDD一定是ShuffledRDD类型的,因为对源RDD进行排序,必须进行Shuffle操作,而Shuffle操作的结果RDD就是ShuffledRDD。其实这个函数的实现很优雅,里面用到了RangePartitioner,它可以使得相应的范围Key数据分到同一个partition中,然后内部用到了mapPartitions对每个partition中的数据进行排序,而每个partition中数据的排序用到了标准的sort机制,避免了大量数据的shuffle。下面对sortByKey的使用进行说明:

01 /**
02  * User: 过往记忆
03  * Date: 14-12-26
04  * Time: 上午10:16
05  * bolg: http://www.iteblog.com
06  * 本文地址:http://www.iteblog.com/archives/1240
07  * 过往记忆博客,专注于hadoop、hive、spark、shark、flume的技术博客,大量的干货
08  * 过往记忆博客微信公共帐号:iteblog_hadoop
09  */
10 scala> val =sc.parallelize(List("wyp""iteblog""com""397090770""test"), 2)
11 a: org.apache.spark.rdd.RDD[String] =
12 ParallelCollectionRDD[30] at parallelize at <console>:12
13  
14 scala> val = sc. parallelize (1 to a.count.toInt , 2)
15 b: org.apache.spark.rdd.RDD[Int] =ParallelCollectionRDD[31] at parallelize at <console>:14
16  
17 scala> val = a.zip(b)
18 c: org.apache.spark.rdd.RDD[(String, Int)] =ZippedPartitionsRDD2[32] at zip at <console>:16
19  
20 scala> c.sortByKey().collect
21 res11: Array[(String, Int)] =Array((397090770,4), (com,3), (iteblog,2), (test,5), (wyp,1))

  上面对Key进行了排序。细心的读者可能会问,soryKy函数中的第一个参数可以对排序方式进行重写。为什么sortByKey没有呢?难道只能用默认的排序规则。不是,是有的。其实在OrderedRDDFunctions类中有个变量ordering它是隐形的:private val ordering = implicitly[Ordering[K]]。他就是默认的排序规则,我们可以对它进行重写,如下:

01 scala> val = sc.parallelize(List(3,1,9,12,4))
02 b: org.apache.spark.rdd.RDD[Int] =ParallelCollectionRDD[38] at parallelize at <console>:12
03  
04 scala> val = b.zip(a)
05 c: org.apache.spark.rdd.RDD[(Int, String)] =ZippedPartitionsRDD2[39] at zip at <console>:16
06  
07 scala> c.sortByKey().collect
08 res15: Array[(Int, String)] =Array((1,iteblog), (3,wyp), (4,test), (9,com), (12,397090770))
09  
10 scala> implicit val sortIntegersByString = new Ordering[Int]{
11      override def compare(a: Int, b: Int) =
12      | a.toString.compare(b.toString)}
13 sortIntegersByString: Ordering[Int] = $iwC$$iwC$$iwC$$iwC$$iwC$$anon$1@5d533f7a
14  
15 scala>  c.sortByKey().collect
16 res17: Array[(Int, String)] =Array((1,iteblog), (12,397090770), (3,wyp), (4,test), (9,com))

  例子中的sortIntegersByString就是修改了默认的排序规则。这样将默认按照Int大小排序改成了对字符串的排序,所以12会排序在3之前。

fold vs flodleft vs foldright

从本质上说,fold函数将一种格式的输入数据转化成另外一种格式返回。fold, foldLeft和foldRight这三个函数除了有一点点不同外,做的事情差不多。我将在下文解释它们的共同点并解释它们的不同点。
  我将从一个简单的例子开始,用fold计算一系列整型的和。

1 val numbers = List(54862)
2 numbers.fold(0) { (z, i) =>
3   z + i
4 }
5 // result = 25

  List中的fold方法需要输入两个参数:初始值以及一个函数。输入的函数也需要输入两个参数:累加值和当前item的索引。那么上面的代码片段发生了什么事?
  代码开始运行的时候,初始值0作为第一个参数传进到fold函数中,list中的第一个item作为第二个参数传进fold函数中。
  1、fold函数开始对传进的两个参数进行计算,在本例中,仅仅是做加法计算,然后返回计算的值;
  2、Fold函数然后将上一步返回的值作为输入函数的第一个参数,并且把list中的下一个item作为第二个参数传进继续计算,同样返回计算的值;
  3、第2步将重复计算,直到list中的所有元素都被遍历之后,返回最后的计算值,整个过程结束;
  4、这虽然是一个简单的例子,让我们来看看一些比较有用的东西。早在后面将会介绍foldLeft函数,并解释它和fold之间的区别,目前,你只需要想象foldLeft函数和fold函数运行过程一样。

  下面是一个简单的类和伴生类:

1 class Foo(val name: String, val age: Int, val sex: Symbol)
2  
3 object Foo {
4   def apply(name: String, age: Int, sex: Symbol) = new Foo(name, age, sex)
5 }

  假如我们有很多的Foo实例,并存在list中:

1 val fooList = Foo("Hugh Jass"25'male) ::
2               Foo("Biggus Dickus", 43, 'male) ::
3               Foo("Incontinentia Buttocks"37, 'female) ::
4               Nil

  我们想将上面的list转换成一个存储[title] [name], [age]格式的String链表:

01 val stringList = fooList.foldLeft(List[String]()) { (z, f) =>
02   val title = f.sex match {
03     case 'male => "Mr."
04     case 'female ="Ms."
05   }
06   :+ s"$title ${f.name}, ${f.age}"
07 }
08  
09 // stringList(0)
10 // Mr. Hugh Jass, 25
11  
12 // stringList(2)
13 // Ms. Incontinentia Buttocks, 37

  和第一个例子一样,我们也有个初始值,这里是一个空的String list,也有一个操作函数。在本例中,我们判断了性别,并构造了我们想要的String,并追加到累加器中(这里是一个list)。
  fold, foldLeft, and foldRight之间的区别
  主要的区别是fold函数操作遍历问题集合的顺序。foldLeft是从左开始计算,然后往右遍历。foldRight是从右开始算,然后往左遍历。而fold遍历的顺序没有特殊的次序。



CombineByKey

def combineByKey[C](createCombiner: V => C, mergeValue: (C, V) => C,
    mergeCombiners: (C, C) => C) : RDD[(K, C)]
def combineByKey[C](createCombiner: V => C, mergeValue: (C, V) => C,
    mergeCombiners: (C, C) => C, numPartitions: Int): RDD[(K, C)]
def combineByKey[C](createCombiner: V => C, mergeValue: (C, V) => C,
    mergeCombiners: (C, C) => C, partitioner: Partitioner, mapSideCombine:
    Boolean = true, serializer: Serializer = null): RDD[(K, C)]


  第一个和第二个函数都是基于第三个函数实现的,使用的是HashPartitioner,Serializer为null。而第三个函数我们可以指定分区,如果需要使用Serializer的话也可以指定。combineByKey函数比较重要,我们熟悉地诸如aggregateByKey、foldByKey、reduceByKey等函数都是基于该函数实现的。默认情况会在Map端进行组合操作。
combineByKey函数主要接受了三个函数作为参数,分别为createCombiner、mergeValue、mergeCombiners。这三个函数足以说明它究竟做了什么。理解了这三个函数,就可以很好地理解combineByKey。


要理解combineByKey(),要先理解它在处理数据时是如何处理每个元素的。由于combineByKey()会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就和之前的键相同。combineByKey()的处理流程如下:


1.如果是一个新的元素,此时使用createCombiner()来创建那个键对应的累加器的初始值。(!注意:这个过程会在每个分区第一次出现各个键时发生,而不是在整个RDD中第一次出现一个键时发生。)


2.如果这是一个在处理当前分区中之前已经遇到键,此时combineByKey()使用mergeValue()将该键的累加器对应的当前值与这个新值进行合并。


3.由于每个分区都是独立处理的,因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器,就需要使用用户提供的mergeCombiners()将各个分区的结果进行合并。

combineByKey的功能是对RDD中的数据集按照Key进行聚合(想象下Hadoop MapReduce的Combiner,用于Map端做Reduce)。聚合的逻辑是通过自定义函数提供给combineByKey。
从上面的源代码中可以看到,combineByKey是把(K,V)类型的RDD转换为(K,C)类型的RDD,C和V可以不一样。
combineByKey函数需要三个重要的函数作为参数
createCombiner:在遍历RDD的数据集合过程中,对于遍历到的(k,v),如果combineByKey第一次遇到值为k的Key(类型K),那么将对这个(k,v)调用combineCombiner函数,它的作用是将v转换为c(类型是C,聚合对象的类型,c作为局和对象的初始值)
mergeValue:在遍历RDD的数据集合过程中,对于遍历到的(k,v),如果combineByKey不是第一次(或者第二次,第三次…)遇到值为k的Key(类型K),那么将对这个(k,v)调用mergeValue函数,它的作用是将v累加到聚合对象(类型C)中,mergeValue的
类型是(C,V)=>C,参数中的C遍历到此处的聚合对象,然后对v进行聚合得到新的聚合对象值
mergeCombiners:因为combineByKey是在分布式环境下执行,RDD的每个分区单独进行combineByKey操作,最后需要对各个分区的结果进行最后的聚合,它的函数类型是(C,C)=>C,每个参数是分区聚合得到的聚合对象。




假设有一组气象数据,每行数据包含日期和当天的气温(比如,20150601 27),那么可以用combineByKey求每月的平均温度


val rdd = sc.textFile("气象数据") 
val rdd2 = rdd.map(x=>x.split(" ")).map(x => (x(0).substring("从年月日中提取年月"),x(1).toInt)) 
val createCombiner = (k: String, v: Int)=> {(v,1)} 
val mergeValue = (c:(Int, Int), v:Int) => {(c._1 + v, c._2 + 1)}  
val mergeCombiners = (c1:(Int,Int),c2:(Int,Int))=>{(c1._1 + c2._1, c1._2 + c2._2)}   
val vdd3 = vdd2.combineByKey(createCombiner, mergeValue, mergeCombiners)  
rdd3.foreach(x=>println(x._1 + ": average tempreture is " + x._2._1/x._2._2)

示例1、
val data = sc.parallelize(List(("iteblog", 1), ("bbs", 1), ("iteblog", 3)))
val createCombiner = (v: Int)=> {(v,1)}
val mergeValue = (c:(Int, Int), v:Int) => {(c._1 + v, c._2 + 1)}
val mergeCombiners = (c1:(Int,Int),c2:(Int,Int))=>{(c1._1 + c2._1, c1._2 + c2._2)}  
val rdd3 = data.combineByKey( createCombiner, mergeValue, mergeCombiners )
rdd3.foreach(x=>println(x._1 + ": average tempreture is " + x._2._1/x._2._2))


示例2、
/**
 * User: 过往记忆
 * Date: 15-03-19
 * Time: 上午08:24
 * bolg: http://www.iteblog.com
 * 本文地址:http://www.iteblog.com/archives/1291
 * 过往记忆博客,专注于hadoop、hive、spark、shark、flume的技术博客,大量的干货
 * 过往记忆博客微信公共帐号:iteblog_hadoop
 */
scala> val data = sc.parallelize(List((1, "www"), (1, "iteblog"), (1, "com"), 
  (2, "bbs"), (2, "iteblog"), (2, "com"), (3, "good")))
data: org.apache.spark.rdd.RDD[(Int, String)] = 
  ParallelCollectionRDD[15] at parallelize at <console>:12


scala> val result = data.combineByKey(x=>List(x), 
  (x: List [String], y: String) => y :: x, (x: List[String], y: List[String]) => x ::: y)
result: org.apache.spark.rdd.RDD[(Int, List[String])] = 
  ShuffledRDD[19] at combineByKey at <console>:14


scala> result.collect
res20: Array[(Int, List[String])] = Array((1,List(www, iteblog, com)),
   (2,List(bbs, iteblog, com)), (3,List(good)))


scala> val data = sc.parallelize(List(("iteblog", 1), ("bbs", 1), ("iteblog", 3)))
data: org.apache.spark.rdd.RDD[(String, Int)] = 
  ParallelCollectionRDD[24] at parallelize at <console>:12


scala> val result = data.combineByKey(x => x, 
  (x: Int, y:Int) => x + y, (x:Int, y: Int) => x + y)
result: org.apache.spark.rdd.RDD[(String, Int)] = 
  ShuffledRDD[25] at combineByKey at <console>:14


scala> result.collect
res27: Array[(String, Int)] = Array((iteblog,4), (bbs,1))


自定义分区:

我们都知道Spark内部提供了HashPartitionerRangePartitioner两种分区策略(这两种分区的代码解析可以参见:《Spark分区器HashPartitioner和RangePartitioner代码详解》),这两种分区策略在很多情况下都适合我们的场景。但是有些情况下,Spark内部不能符合咱们的需求,这时候我们就可以自定义分区策略。为此,Spark提供了相应的接口,我们只需要扩展Partitioner抽象类,然后实现里面的三个方法:

01 package org.apache.spark
02  
03 /**
04  * An object that defines how the elements in a key-value pair RDD are partitioned by key.
05  * Maps each key to a partition ID, from 0 to `numPartitions - 1`.
06  */
07 abstract classPartitioner extendsSerializable {
08   def numPartitions:Int
09   def getPartition(key:Any):Int
10 }

  def numPartitions: Int:这个方法需要返回你想要创建分区的个数;
  def getPartition(key: Any): Int:这个函数需要对输入的key做计算,然后返回该key的分区ID,范围一定是0到numPartitions-1
  equals():这个是Java标准的判断相等的函数,之所以要求用户实现这个函数是因为Spark内部会比较两个RDD的分区是否一样。

  假如我们想把来自同一个域名的URL放到一台节点上,比如:http://www.iteblog.comhttp://www.iteblog.com/archives/1368,如果你使用HashPartitioner,这两个URL的Hash值可能不一样,这就使得这两个URL被放到不同的节点上。所以这种情况下我们就需要自定义我们的分区策略,可以如下实现:

01 package com.iteblog.utils
02  
03 import org.apache.spark.Partitioner
04  
05 /**
06  * User: 过往记忆
07  * Date: 2015-05-21
08  * Time: 下午23:34
09  * bolg: http://www.iteblog.com
10  * 本文地址:http://www.iteblog.com/archives/1368
11  * 过往记忆博客,专注于hadoop、hive、spark、shark、flume的技术博客,大量的干货
12  * 过往记忆博客微信公共帐号:iteblog_hadoop
13  */
14  
15 class IteblogPartitioner(numParts:Int) extends Partitioner {
16   override defnumPartitions:Int =numParts
17  
18   override defgetPartition(key:Any):Int ={
19     val domain =newjava.net.URL(key.toString).getHost()
20     val code =(domain.hashCode %numPartitions)
21     if(code < 0) {
22       code + numPartitions
23     else{
24       code
25     }
26   }
27  
28   override defequals(other:Any):Boolean =other match{
29     case iteblog:IteblogPartitioner =>
30       iteblog.numPartitions ==numPartitions
31     case_=>
32       false
33   }
34  
35   override defhashCode:Int =numPartitions
36 }

因为hashCode值可能为负数,所以我们需要对他进行处理。然后我们就可以在partitionBy()方法里面使用我们的分区:

1 iteblog.partitionBy(newIteblogPartitioner(20))

  类似的,在Java中定义自己的分区策略和Scala类似,只需要继承org.apache.spark.Partitioner,并实现其中的方法即可。

  在Python中,你不需要扩展Partitioner类,我们只需要对iteblog.partitionBy()加上一个额外的hash函数,如下:

1 import urlparse
2  
3 def iteblog_domain(url):
4   return hash(urlparse.urlparse(url).netloc)
5  
6 iteblog.partitionBy(20, iteblog_domain)


mvn 创建scala工程

一般我们都是用SBT来维护Scala工程,不过我们也是可以使用Maven来创建Scala工程。在命令行使用下面语句即可创建Scala工程:

01 /**
02  * User: 过往记忆
03  * Date: 2015-05-24
04  * Time: 上午11:05
05  * bolg: http://www.iteblog.com
06  * 本文地址:http://www.iteblog.com/archives/1370
07  * 过往记忆博客,专注于hadoop、hive、spark、shark、flume的技术博客,大量的干货
08  * 过往记忆博客微信公共帐号:iteblog_hadoop
09  */
10  
11 mvn archetype:generate -DarchetypeGroupId=net.alchim31.maven
12     -DarchetypeArtifactId=scala-archetype-simple
13     -DremoteRepositories=http://scala-tools.org/repo-releases
14     -DgroupId=com.iteblog
15     -DartifactId=iteblog
16     -Dversion=1.0-SNAPSHOT

  在创建的时候使用到scala-archetype-simple模版,如果不指定该模版的版本,默认是使用最新版的。目前最新版的scala-archetype-simple为1.5,其中的Scala版本是2.10.0,已经不是最新版的Scala了,大家可以根据自己的情况去修改pom.xml文件里面的Scala、ScalaTest、Surefire以及scala-maven-plugin的版本。

  当运行完上面的命令之后,会在运行命令的当前文件夹下面生成名字为iteblog的Scala工程,文件夹的目录结构树如下:

01 [iteblog@localhost ~/iteblog]$ tree
02 .
03 |-- pom.xml
04 `-- src
05     |-- main
06     |   `-- scala
07     |       `-- com
08     |           `-- iteblog
09     |               `-- App.scala
10     `-- test
11         `-- scala
12             `-- samples
13                 |-- junit.scala
14                 |-- scalatest.scala
15                 `-- specs.scala
16  
17 8 directories, 5 files

  创建的工程默认的包名是使用-DgroupId的值。默认的情况会生成一个用于测试的App.scala的文件。

  当然,在使用scala-archetype-simple模版的时候我们还可以选择版本,只需要加上-DarchetypeVersion选项即可。同时我们还可以通过-Dpackage选项指定我们创建工程的包名

1 mvn archetype:generate -DarchetypeGroupId=net.alchim31.maven
2     -DarchetypeArtifactId=scala-archetype-simple
3     -DremoteRepositories=http://scala-tools.org/repo-releases
4     -DarchetypeVersion=1.5
5     -DgroupId=com.iteblog
6     -DartifactId=iteblog
7     -Dversion=1.0-SNAPSHOT
8     -Dpackage=com.iteblog



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值