Learning Spark阅读笔记2

Working with Key/Value Pairs

Key/Value RDDs通常被用来执行aggregations,我们经常会做一些初始化ETL(extract, transform, load)来得到我们的key/value数据。

使用可控制的partitioning,应用程序能够减少通信消耗通过确保数据同时被访问到,在同一个节点上。

Creating Pair RDDs

有些加载数据集会直接得到key/value数据,有些需要我们将它转换成pair RDD。可以使用map()来实现,例如:

val pairs = lines.map(x => (x.split(" ")(0), x))

当从内存的集合中创建pair RDD,只需要调用SparkContext.parallelize()。

Transformations on Pair RDDs

Pair RDDs可以使用所有的适用于标准RDDs的transformation。下面列出常用的pair RDDs transformation。

  1. reduceByKey(func) Combine values with the same key.
  2. groupByKey() Group values with the same key.
  3. combineByKey(createCombiner, mergeValue, mergeCombiners, partitioner)
  4. mapValues(func) Apply a function to each value of a pair RDD without changing the key.
  5. flatMapValues(func)
  6. keys()
  7. values()
  8. sortByKey()
  9. substractByKey(other)
  10. join(other)
  11. rightOuterJoin(other) where the key must be present in the first RDD.
  12. leftOuterJoin(other)
  13. cogroup(other) Group data from both RDDs sharing the same key.

Aggregations

reduceByKey() 不是actions,因为数据集中可能会有很多keys。举个例子,计算键的平均值:

rdd.mapValues(x => (x, 1)).reduceByKey((x, y) => (x._1 + y._1, x._2 + y._2))

注意,在执行reduceByKey()和foldByKey()将会自动在每一个机器本地先执行combining,不需要手动设置。更一般的combineByKey()将会允许自定义combining的行为。

combineByKey()是最基础的per-key aggregation函数,很多其他的per-key combiners都用它来实现。要理解combineByKey的行为,需要知道它的执行过程:

  1. 在一个partition上遍历每一个元素,对于碰到的元素,它的键要么之前碰到过,要么是新遇到的。
  2. 如果是新的元素,就会使用我们提供的createCombiner()函数,来创建在那个键上开始accumulator的初始值。
  3. 如果之间碰到过,就是调用我们提供的函数mergeValue(),使用accumulator的值和现在碰到的值。
  4. 每一个partition将会独立执行上面3步,最后merge每个partition的结果时,将会调用我们提供的mergeCombiners()函数。

如果我们需要禁止map-side combines,我们需要指定partitioner。(TODO 什么是map-side combines)

对于combineByKey()举个例子:

val result = input.combineByKey(
  (v) => (v, 1), // createCombiner
  (acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1),    // mergeValue
  (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)    // mergeCombiners
  ).map{ case (key, value) => (key, value._1 / value._2.toFloat) }
result.collectAsMap().map(println(_))

Tuning the level of parallelism

当执行aggregations和grouping操作时,我们可以指定partitions的个数。举个例子:

val data = Seq(("a", 3), ("b", 4), ("a", 1))
sc.parallelize(data).reduceByKey((x, y) => x + y) //Default parallelism
sc.parallelize(data).reduceByKey((x, y) => x + y, 10) // Custom parallelism

有时候,我们在grouping和aggregation操作之外要更改partitioning,可以使用repartition()函数,但是代价非常高,如果更改的partition数目比原来小,Spark有个优化的函数,coalesce(),在使用之前可以使用rdd.partitions.size()来查看partition的数目。

Grouping Data

如果一个RDD的键值是K,值是V,那么应用groupByKey()之后将会得到(K,Iterable[V]),注意如果先使用groupByKey,然后使用reduce或者fold作用在值上,不如世界使用aggregation函数(例如reduceByKey)来的有效。

cogroup可以从多个RDD上group相同的key。

Joins

inner join:

storeAddress = {
  (Store("Ritual"), "1026 Valencia St"), (Store("Philz"), "748 Van Ness Ave"),
  (Store("Philz"), "3101 24th St"), (Store("Starbucks"), "Seattle")}
storeRating = {
  (Store("Ritual"), 4.9), (Store("Philz"), 4.8))}
storeAddress.join(storeRating) == {
  (Store("Ritual"), ("1026 Valencia St", 4.9)),
  (Store("Philz"), ("748 Van Ness Ave", 4.8)),
  (Store("Philz"), ("3101 24th St", 4.8))}

leftOuterJoin有源RDD的所有键,值是对应的两个RDD的,如果右边RDD没有这个键,将表示成None,有的话将是Option类型,表示可能丢失值。

rightOuterJoin和leftOuterJoin相反。

storeAddress.leftOuterJoin(storeRating) ==
{(Store("Ritual"),("1026 Valencia St",Some(4.9))),
  (Store("Starbucks"),("Seattle",None)),
  (Store("Philz"),("748 Van Ness Ave",Some(4.8))),
  (Store("Philz"),("3101 24th St",Some(4.8)))}
storeAddress.rightOuterJoin(storeRating) ==
{(Store("Ritual"),(Some("1026 Valencia St"),4.9)),
  (Store("Philz"),(Some("748 Van Ness Ave"),4.8)),
  (Store("Philz"), (Some("3101 24th St"),4.8))}

Sorting Data

可以在sortByKey中传递比较函数进行自定义的比较。

Actions Available on Pair RDDs

所有使用在基础RDD上的action操作都能使用,同时也增加了适用与pair的action操作。

  1. countByKey(): Count the number of elements for each key.
  2. collectAsMap(): Collect the result as a map to provide easy lookup.
  3. lookup(key): Return all values associated with the provided key.

Data Partitioning

在分布式计算中,Spark可以控制RDD's的分块来减少通信开销,但是要记住分块并不见得在所有程序中都有帮助,只有在某个数据集被频繁重复使用,并且是key-oriented的操作,例如join,才会有帮助。

Spark的partition对所有的key/value对的RDD都可使用,将会令系统基于在key上的函数组织元素。确保那一组key将会出现在某些node上。举个例子:

val sc = new SparkContext(...)
val userData = sc.sequenceFile[UserID, UserInfo]("hdfs://...").persist()

def 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)) => // Expand the tuple into its components
      !userInfo.topics.contains(linkInfo.topic)
  }.count()
  println("Number of visits to non-subscribed topics: " + offTopicVisits)
}

上面的代码是将userData和events作join操作,但是运行是很没有效率的,因为每次调用processNewLogs函数时,join都会执行,但是不知道key是怎么分布的,将会hash两个数据集上的key,通过网络传送相同的key,将会造成很大的网络开销。示意图如下:

1036292-20161120144736607-1455140729.png

但是使用partitionBy将会减少通信消耗:

val sc = new SparkContext(...)
val userData = sc.sequenceFile[UserID, UserInfo]("hdfs://...")
                 .partitionBy(new HashPartitioner(100)) // Create 100 partitions
                 .persist()

现在Spark就知道userData是如何被分块的,join将会只shuffle events RDD,示意图如下:

1036292-20161120145009170-1285218478.png

实施上,很多操作会自动地将RDD进行某种方式的分块,例如sortByKey()将会使用range-partitioned,groupByKey()将会使用hash-partitioned。除了join,还有其他的操作会利用分块的信息。

Determining an RDD's Partitioner

可以查看partitioner属性来查看使用了什么partitioner。

scala> val pairs = sc.parallelize(List((1, 1), (2, 2), (3, 3)))
pairs: spark.RDD[(Int, Int)] = ParallelCollectionRDD[0] at parallelize at <console>:12

scala> pairs.partitioner
res0: Option[spark.Partitioner] = None

scala> val partitioned = pairs.partitionBy(new spark.HashPartitioner(2))
partitioned: spark.RDD[(Int, Int)] = ShuffledRDD[1] at partitionBy at <console>:14

scala> partitioned.partitioner
res1: Option[spark.Partitioner] = Some(spark.HashPartitioner@5147788d)

Operations That Benefit from Partitioning

很多涉及到根据key来shuffle数据的操作将会受益于partitioning。

Operations That Affect Partitioning

Spark知道每个操作怎么影响partitioning,自动地设置RDD的partitioner。总的来说,以下的会设置输出RDD的partitioner:

  • cogroup()
  • groupWith()
  • join()
  • leftOuterJoin()
  • rightOuterJoin()
  • groupByKey()
  • reduceByKey()
  • combineByKey()
  • partitionBy()
  • sort()
  • mapValues() 取决与父RDD
  • flatMapValues() 取决与父RDD
  • filter() 取决与父RDD

当然上面的很多方法也可以设置输出结果的partitioning。

Custom Partitioners

除了Spark自带的HashPartitioner和RangePartitioner,用户也可以自定义Partitioner。需要继承org.apache.spark.Partitioner,并实现3个方法:

  1. numPartitions: Int 返回创建的partition的数量
  2. getPartition(key: Any): Int 对给定的key返回partition的ID
  3. equals() Spark要比较两个两个RDD的分区是否相同

举个例子,在PageRank中键是URL,但是我们希望将相同域名下的归到一起,所以代码如下:

class DomainNamePartitioner(numParts: Int) extends Partitioner {
  override def numPartitions: Int = numParts
  override def getPartition(key: Any): Int = {
  val domain = new Java.net.URL(key.toString).getHost()
  val code = (domain.hashCode % numPartitions)
  if (code < 0) {
    code + numPartitions // Make it non-negative
  } else {
    code
  }
}
// Java equals method to let Spark compare our Partitioner objects
  override def equals(other: Any): Boolean = other match {
    case dnp: DomainNamePartitioner =>
      dnp.numPartitions == numPartitions
    case _ =>
      false
  }
}

注意这里我们的equals()方法,测试other是否是DomainNamePartioner,和Java里的instanceof相同。

转载于:https://www.cnblogs.com/fang-jie/articles/6079211.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spark SQL是Spark中用于处理结构化数据的模块。它提供了一种基于DataFrame和SQL的编程接口,可以方便地进行数据分析和处理。Spark SQL支持多种数据源,包括Hive、JSON、Parquet等,可以通过SQL语句或DataFrame API进行数据查询和操作。Spark SQL还支持用户自定义函数(UDF)和聚合函数(UDAF),可以满足更复杂的数据处理需求。Spark SQL的优势在于它可以与Spark的其他模块无缝集成,如Spark Streaming、MLlib等,可以构建完整的数据处理和分析流程。 ### 回答2: 本篇笔记主要是介绍Spark SQL的基本概念和编程模型。 Spark SQL是面向Spark计算引擎的一种高性能的分布式数据处理技术,它提供一种基本的高度抽象的编程模型,使得开发大规模的数据仓库和数据分析应用变得容易和高效。 Spark SQL最核心的概念就是DataFrames,DataFrame是RDD的超集,提供了更高层次的抽象和对数据的结构化的处理能力,在数据处理的过程中常常会用到一些基本的操作:过滤、选择、聚合、排序等等,而这些操作都可以一步一步地以DataFrame为基础完成。 在使用Spark SQL的过程中,可以通过DataFrame API和Spark SQL语言两种方式进行编程。DataFrame API是Spark SQL提供的一种编程API,它提供了常见的操作,如选择、过滤和聚合等。而Spark SQL语言则是一种基于SQL的编程语言,和传统的SQL查询语言类似,可以通过SQL查询语句来对数据进行查询和操作。Spark SQL可以支持多种数据源,包括JSON、Parquet、ORC、Hive、JDBC等等,因此可以轻松地读取和处理不同类型的数据源。 Spark SQL还提供了高级的功能,如User-Defined Functions(UDFs)、Window Functions和Structured Streaming等等。UDFs允许开发者自定义函数并在Spark SQL中使用,将SQL和代码结合起来,提高了处理数据的灵活性和可扩展性;Window Functions则是一种用来进行滑动窗口操作的函数,常常用于计算数据的局部或全局统计量;Structured Streaming提供了数据流处理的能力,并且实现了端到端的Exactly-Once语义。 总之,Spark SQL提供了很多的功能和便利,特别是在大数据处理和分析领域,它的优势尤为突出。结合Spark的强大计算能力和Spark SQL的抽象编程模型,在大规模的数据分析和仓库方面都具有非常高的可扩展性和灵活性。 ### 回答3: Spark SQL是Spark生态系统中的一个组件,它负责处理结构化数据。它提供了SQL查询和DataFrame API,可以从不同的数据源中读取和处理数据。Spark SQL能够理解SQL语言,这使得开发人员可以使用传统的SQL查询方式来处理数据,同时还可以利用Spark的优势,例如分布式计算和内存缓存。 Spark SQL支持许多不同类型的数据源,包括Hive表、传统的RDD、Parquet文件、JSON文件、CSV文件和JDBC数据源等。Spark SQL可以通过使用数据源API将这些数据源加载到Spark中,然后可以在Spark中处理和查询这些数据。 Spark SQL还支持特定于数据源的优化器和执行引擎,这允许Spark SQL针对不同的数据源执行优化操作。例如,使用Hive数据源时,Spark SQL会使用Hive的元数据来优化查询计划。当使用Parquet文件格式时,Spark SQL会使用Parquet文件中的元数据来优化查询计划。 在Spark SQL中,DataFrame是一种非常重要的概念。它是一种强类型的分布式数据集,可以使用DataFrame API进行操作。DataFrame API是一种更面向数据的API,例如过滤数据、聚合数据等。Spark SQL中的DataFrame可以看作是类似于表的对象,它可以和Spark SQL中的SQL查询混合使用。 除了DataFrame API和SQL查询,Spark SQL还支持UDF(用户自定义函数)。UDF允许用户在SQL查询或DataFrame API中定义自己的函数,以实现更复杂的数据操作。使用UDF时,用户可以使用累加器和广播变量等Spark的分布式计算功能,使得UDF具备高性能和可伸缩性。 总之,Spark SQL是大数据处理领域中一种非常方便和强大的处理结构化数据的工具。它可以方便地与其他Spark组件结合使用,例如Spark Streaming、Spark MLlib等。使用Spark SQL,开发人员可以在不同的数据源之间轻松地查询和转换数据,并利用Spark分布式计算的优势,实现高性能和可伸缩性的数据处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值