spark学习笔记(3)

数据分区

  1. 获取数据分区
  • 调用RDD数据的partitioner属性,得到scala.Option对象,即表示scala中用来存放可能存在的对象的容器类。
val pairs = sc.parallelize(list((1,1), (2,2), (3,3))) // 初始化原始RDD数据
pairs.partitioner // output: Option[spark.Partitioner] = None
//调用partitioner方法,查看该RDD分区情况,得到结果未分区
val partitioned = pairs.partitionBy(new spark.HashPartitioner(2)).persist() // 将数据分成两个区。加上persist保证变换后的数据是可持久化的,以便后续操作中直接使用
pairs.partitioner // output: Option[spark.Partitioner] = Some(spark.HashPartitioner@5147788d)
  1. 从数据分区中获益的操作
  • 从数据分区中获益的操作:cogroup(), groupwith(), join(), leftOuterJoin(), rightOuterJoin(), groupbyKey(), reduceByKey(), combineKey(), lookup()
  • 获益的机理:对于上述二元操作而言,预先进行数据分区导致其中至少一个RDD不发生数据混洗。
  1. 影响分区方式的操作
  • 会为生成结果RDD按特定方式分区的操作:cogroup(), groupwith(), join(), leftOuterJoin(), rightOuterJoin(), groupbyKey(), reduceByKey(), combineKey(), partitionBy(), sort(), mapValues()(若父RDD有分区方式), flatMapValues()(若父RDD有分区方式), filter()(若父RDD有分区方式).其他操作的生成结果不会有特定的分区方式
  • 对二元操作而言,数据的分区方式取决于父RDD的分区方式(包括哈希分区、分区的数量、操作的并行度)
  1. 自定义分区方式
    需要扩展org.apache.spark.Partitioner类中的三个函数:
  • numPartitions
  • getPartition
  • equals

实例:

  • 基于域名的分区。即认为同一个域名下的网页更有可能是相互链接的,而在pagerank例中,需要对相同域名下的网页做join工作,因此将它们放在同一个分区会更好,这可以大大缩短通信时间。
import org.apache.spark.Partitioner._
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
			} else {
    
    
				code
			}
	}
}
// 用来让spark区分分区函数对象的Java equals 方法
override def equals(other: Any): Boolean = other match {
    
    
	dnp.numPartitions == numPartitions
	case _ => false
}

数据读取与保存

文本文件

读取

  • 读取单个文件
val input = sc.textFile(data_dir)
  • 一次性读取多个文件(均放在同一目录下)
    方法1 用textFile()方法,以数据所在目录为参数
    方法2 用wholeTextFiles()方法,该方法返回一个pair RDD
val input = sc.wholeTextFiles(data_dir)
val result = input.mapValues {
    
    
	y => val nums = y.split(" ").map(x => x.Double)
	nums.sum / nums.size.toDouble
}

保存

result.saveAsTextFile(outputFile)

JSON

读取

先读取,再解析。由于数据的格式可能不正确,读取过程中无法顺利读取。对于较小的数据集来说,遇到错误时即停止程序;对于较大的数据集来说,尝试用累加器跟踪错误的个数。
例:

import com.fasterxml.jackson.module.scala.{
    
    DefaultScalaModule, experimental.ScalaObjectMapper}
import com.fasterxml.jackson.databind.{
    
    ObjectMapper, DeserializationFeature}
case class Person(name: String, sex: Boolean)

// 将json文件解析为特定的case class. 使用flatMap实现。若遇格式错误则返回空
val result = input.flatMap(record => {
    
    
	try {
    
    
		Some(mapper.readValue(record, classOf[person]))
	}
	catch {
    
    
		case e: Exception => None
		}
	}) 

保存

result.filter(p => p.sex).map(mapper.writeValueAsString(_)).saveAsTextFile(outputFile)

CSV

读取

先读取,再解析。

import java.io.StringReader
import au.com.bytecode.opencsv.CSVReader
val input = sc.textFile(inputFile)
val result = input.map{
    
    line => 
	val reader = new CSVReader(new StringReader(line));
	reader.readNext();
}

保存

csvFile.map(x => List(x.name, x.sex).toArray)
	.mapPartitions{
    
    people => 
	val stringWriter = new stringWriter();
	val csvWriter = new csvWriter(stringWriter);
	csvWriter.writeAll(people.toList);
	Iterator(stringWriter.toString)
	}.saveAsTextFile(outFile)

SequenceFile

SequenceFile是由Hadoop的Writable接口的元素组成的。scala/java中常见的数据类型在Hadoop中,有其对应的writable类型。在Hadoop系统中,数据很有可能是以SequenceFile的形式而用的。

读取

val data = sc.SequenceFile(inFile, classOf[Text], classOf[IntWritable]).map{
    
    case (x, y) => (x.toString, y.get())}

存储

  • 将scala原生类型转化为SequenceFile
val data = sc.parallelize(List(("apple", 1), ("orange", 2)))
data.saveAsSequenceFile(outputFile)
  • 先进行类型转换(变成scala原生类型,比如PairRDD),再保存

猜你喜欢

转载自blog.csdn.net/weixin_42365868/article/details/110677728