RDD,全称为Resilient Distributed Datasets,是一个容错的、并行的数据结构,可以让用户显式地将数据存储到磁盘和内存中,并能控制数据的分区。
RDD是只读的、分区记录的集合。RDD不需要物化。RDD含有如何从其他RDD衍生(即计算)出本RDD的相关信息(即Lineage),据此可以从物理存储的数据计算出相应的RDD分区。
每个RDD有5个主要的属性:
Internally, each RDD is characterized by five main properties:
- A list of partitions
- A function for computing each split
- A list of dependencies on other RDDs
- Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
- Optionally, a list of preferred locations to compute each split on (e.g. block locations for
an HDFS file)
注意:
本文代码以scala编码。
Spark 2.0才有 SparkSession,2.0之前使用SparkContext
DataSet类型始于Spark 1.6,DataFrame类型始于Spark 1.3
一、RDD创建:
RDD的创建方式有3种:
1. 从程序中的集合创建,即利用并行集合(parallelized collection)
2. 从已有的RDD创建
3. 从外部储存系统引用创建(e.g. HDFS, Hbase, shared file system)
Spark 配置代码(2.0 之前的版本):
import org.apache.spark._ val conf = new SparkConf().setAppName("applicationName").setMaster("local") // 本地环境运行 val sc = new SparkContext(conf) val sqlContext = new org.apache.spark.sql.SQLContext(sc)
1. 并行化创建
如果要通过并行化集合来创建RDD,需要针对程序中的集合(collection),调用SparkContext中的parallelize()方法。Spark会将集合中的数据拷贝到集群上去,形成一个分布式的数据集合,也就是一个RDD。即:集合中的部分数据会到一个节点上,而另一部分数据会到其它节点上。然后就可以采用并行的方式来操作这个分布式数据集合。
并行化创建速度是非常快的,但是由于这种方法需要用到一台机器中collection的全部数据,所以这种方式在测试和原型构造之外很少使用。
创建格式:
parallelize[T](seq : scala.Seq[T], numSlices : scala.Int): RDD[T] = { /* compiled code */ }
示例:
val data1=sc.parallelize(Seq(("sun",01),("mon",02),("tue",03), ("wed",04),("thus",05))) val data2=sc.parallelize(Array(1,2,3,4,5)) val sum=data2.reduce(_+_) //对data2数据求和
在创建RDD的过程中,Spark将集合切分成若干partition。Spark 在集群中会为每个partition运行一个task。 Spark官方的建议为集群中的每个CPU创建2-4个partition。Spark默认会根据集群的情况来设置partition的数量。但是也可以在调用parallelize()方法时,传入第二个参数,来设置RDD的partition数量。比如,parallelize(collection, 10) 则创建了10个partition。
另外,还可以用如下命令创建:
makeRDD[T](seq : scala.Seq[T], numSlices : scala.Int = { /* compiled code */ })
将一个scala.seq创建为带numSlices分区的RDD。注意,parallelize方法可在java中使用,java版本只能接收List的集合,而makeRDD只能在scala中使用。这里这种使用方法实际上调用的是parallelize方法。makeRDD还有另外一种实现:
makeRDD[T:ClassTag](seq:Seq[(T, Seq[String])]):RDD[T]
这个函数还为数据提供了位置信息,这里不细致讨论,可参见:https://blog.csdn.net/u012102306/article/details/53099472
2.从已有RDD创建
由于RDD是immutable的,我们无法改变一个已经创建好的RDD。所以只能由已有RDD创建新的RDD,这个过程叫做转换(transformation)。对RDD的操作有两类,一类是transformation,一类叫动作(action)。不同的操作对应不同的函数,这点具体将在第二部分讨论。
从已有RDD创建新RDD的transformation通常涉及:map, map, filter, count, distinct, flatmap 等
例如:
val words=sc.parallelize(Seq("sun", "rises", "in", "the", "east", "and", "sets", "in", “the”, "west")) val wordPair = words.map(w => (w.charAt(0), w)) wordPair.foreach(println)
输出:
可以看到,RDD中的元素其实就是Seq中的元素。
3.从外部储存系统引用创建
任何Hadoop支持的存储类型都可被用来创建RDD,例如:本地文件系统, HDFS, Cassandra, HBase 等。 利用SparkContext的textFile函数来从外部存储创建RDD。
函数定义如下:
/** * Read a text file from HDFS, a local file system (available on all nodes), or any * Hadoop-supported file system URI, and return it as an RDD of Strings. */ def textFile(path: String,minPartitions: Int = defaultMinPartitions): RDD[String] = withScope { assertNotStopped() hadoopFile(path, classOf[TextInputFormat], classOf[LongWritable], classOf[Text],minPartitions).map(pair => pair._2.toString).setName(path) }
分析参数:
path: String 是一个URI,這个URI可以是HDFS、本地文件(全部的节点都可以),或者其他Hadoop支持的文件系统URI返回的是一个字符串类型的RDD,也就是是RDD的内部形式是Iterator[(String)]
minPartitions= math.min(defaultParallelism, 2) 是指定数据的分区,如果不指定分区,当你的核数大于2的时候,不指定分区数那么就是 2
当你的数据大于128M时候,Spark是为每一个快(block)创建一个分片(Hadoop-2.X之后为128M一个block)
创建例:
val rdd = sc.textFile(“/home/hadoop/data.txt”)
//SparkSession版本 Spark 2.0及以上 import org.apache.spark.sql.SparkSession val spark = SparkSession.builder.appName("ExtDataEx1").master("local").getOrCreate() val data0 = spark.read.csv("path/of/csv/file")//得到DATASET类型 val dataRDD1 = spark.read.csv("path/of/csv/file").rdd //读取csv 文件 val dataRDD2 = spark.read.json("path/of/json/file").rdd //读取json 文件,先得到DATASET
类型 val dataRDD3 = spark.read.textFile("path/of/text/file").rdd//读取text文件,先得到类型DATASET of String 类型
二、RDD的储存
此部分转自:https://blog.csdn.net/T1DMzks/article/details/71037850
def saveAsTextFile(path: String): Unit
def saveAsTextFile(path: String, codec: Class[_ <: CompressionCodec]): Unit
saveAsTextFile用于将RDD以文本文件的格式存储到文件系统中。
codec参数可以指定压缩的类名。
var rdd1 = sc.makeRDD(1 to 10,2) scala> rdd1.saveAsTextFile("hdfs://cdh5/tmp/lxw1234.com/") //保存到HDFS hadoop fs -ls /tmp/lxw1234.com Found 2 items -rw-r--r-- 2 lxw1234 supergroup 0 2015-07-10 09:15 /tmp/lxw1234.com/_SUCCESS -rw-r--r-- 2 lxw1234 supergroup 21 2015-07-10 09:15 /tmp/lxw1234.com/part-00000 hadoop fs -cat /tmp/lxw1234.com/part-0000012345678
注意:如果使用rdd1.saveAsTextFile(“file:///tmp/lxw1234.com”)将文件保存到本地文件系统,那么只会保存在Executor所在机器的本地目录。
指定压缩格式保存
rdd1.saveAsTextFile("hdfs://cdh5/tmp/lxw1234.com/",classOf[com.hadoop.compression.lzo.LzopCodec]) hadoop fs -ls /tmp/lxw1234.com -rw-r--r-- 2 lxw1234 supergroup 0 2015-07-10 09:20 /tmp/lxw1234.com/_SUCCESS -rw-r--r-- 2 lxw1234 supergroup 71 2015-07-10 09:20 /tmp/lxw1234.com/part-00000.lzo hadoop fs -text /tmp/lxw1234.com/part-00000.lzo1234567
saveAsSequenceFile用于将RDD以SequenceFile的文件格式保存到HDFS上。
用法同saveAsTextFile。
def saveAsObjectFile(path: String): Unit
saveAsObjectFile用于将RDD中的元素序列化成对象,存储到文件中。
对于HDFS,默认采用SequenceFile保存。
var rdd1 = sc.makeRDD(1 to 10,2) rdd1.saveAsObjectFile("hdfs://cdh5/tmp/lxw1234.com/") hadoop fs -cat /tmp/lxw1234.com/part-00000 SEQ !org.apache.hadoop.io.NullWritable"org.apache.hadoop.io.BytesWritableT12345
def saveAsHadoopFile(path: String, keyClass: Class[], valueClass: Class[], outputFormatClass: Class[_ <: OutputFormat[, ]], codec: Class[_ <: CompressionCodec]): Unit
def saveAsHadoopFile(path: String, keyClass: Class[], valueClass: Class[], outputFormatClass: Class[_ <: OutputFormat[, ]], conf: JobConf = …, codec: Option[Class[_ <: CompressionCodec]] = None): Unit
saveAsHadoopFile是将RDD存储在HDFS上的文件中,支持老版本Hadoop API。
可以指定outputKeyClass、outputValueClass以及压缩格式。
每个分区输出一个文件。
var rdd1 = sc.makeRDD(Array(("A",2),("A",1),("B",6),("B",3),("B",7))) import org.apache.hadoop.mapred.TextOutputFormat import org.apache.hadoop.io.Text import org.apache.hadoop.io.IntWritable rdd1.saveAsHadoopFile("/tmp/lxw1234.com/",classOf[Text],classOf[IntWritable],classOf[TextOutputFormat[Text,IntWritable]]) rdd1.saveAsHadoopFile("/tmp/lxw1234.com/",classOf[Text],classOf[IntWritable],classOf[TextOutputFormat[Text,IntWritable]], classOf[com.hadoop.compression.lzo.LzopCodec])12345678910
def saveAsHadoopDataset(conf: JobConf): Unit
saveAsHadoopDataset用于将RDD保存到除了HDFS的其他存储中,比如HBase。
在JobConf中,通常需要关注或者设置五个参数:
文件的保存路径、key值的class类型、value值的class类型、RDD的输出格式(OutputFormat)、以及压缩相关的参数。
##使用saveAsHadoopDataset将RDD保存到HDFS中
import org.apache.spark.SparkConf import org.apache.spark.SparkContext import SparkContext._ import org.apache.hadoop.mapred.TextOutputFormat import org.apache.hadoop.io.Text import org.apache.hadoop.io.IntWritable import org.apache.hadoop.mapred.JobConf var rdd1 = sc.makeRDD(Array(("A",2),("A",1),("B",6),("B",3),("B",7))) var jobConf = new JobConf() jobConf.setOutputFormat(classOf[TextOutputFormat[Text,IntWritable]]) jobConf.setOutputKeyClass(classOf[Text]) jobConf.setOutputValueClass(classOf[IntWritable]) jobConf.set("mapred.output.dir","/tmp/lxw1234/") rdd1.saveAsHadoopDataset(jobConf) 结果: hadoop fs -cat /tmp/lxw1234/part-00000 A 2 A 1 hadoop fs -cat /tmp/lxw1234/part-00001 B 6 B 3 B 71234567891011121314151617181920212223242526
##保存数据到HBASE
HBase建表:
create ‘lxw1234′,{NAME => ‘f1′,VERSIONS => 1},{NAME => ‘f2′,VERSIONS => 1},{NAME => ‘f3′,VERSIONS => 1}
import org.apache.spark.SparkConf import org.apache.spark.SparkContext import SparkContext._ import org.apache.hadoop.mapred.TextOutputFormat import org.apache.hadoop.io.Text import org.apache.hadoop.io.IntWritable import org.apache.hadoop.mapred.JobConf import org.apache.hadoop.hbase.HBaseConfiguration import org.apache.hadoop.hbase.mapred.TableOutputFormat import org.apache.hadoop.hbase.client.Put import org.apache.hadoop.hbase.util.Bytes import org.apache.hadoop.hbase.io.ImmutableBytesWritable var conf = HBaseConfiguration.create() var jobConf = new JobConf(conf) jobConf.set("hbase.zookeeper.quorum","zkNode1,zkNode2,zkNode3") jobConf.set("zookeeper.znode.parent","/hbase") jobConf.set(TableOutputFormat.OUTPUT_TABLE,"lxw1234") jobConf.setOutputFormat(classOf[TableOutputFormat]) var rdd1 = sc.makeRDD(Array(("A",2),("B",6),("C",7))) rdd1.map(x => { var put = new Put(Bytes.toBytes(x._1)) put.add(Bytes.toBytes("f1"), Bytes.toBytes("c1"), Bytes.toBytes(x._2)) (new ImmutableBytesWritable,put) } ).saveAsHadoopDataset(jobConf) ##结果: hbase(main):005:0> scan 'lxw1234' ROW COLUMN+CELL A column=f1:c1, timestamp=1436504941187, value=\x00\x00\x00\x02 B column=f1:c1, timestamp=1436504941187, value=\x00\x00\x00\x06 C column=f1:c1, timestamp=1436504941187, value=\x00\x00\x00\x07 3 row(s) in 0.0550 seconds123456789101112131415161718192021222324252627282930313233343536
注意:保存到HBase,运行时候需要在SPARK_CLASSPATH中加入HBase相关的jar包。
可参考:http://lxw1234.com/archives/2015/07/332.htm
def saveAsNewAPIHadoopFile[F <: OutputFormat[K, V]](path: String)(implicit fm: ClassTag[F]): Unit
def saveAsNewAPIHadoopFile(path: String, keyClass: Class[], valueClass: Class[], outputFormatClass: Class[_ <: OutputFormat[, ]], conf: Configuration = self.context.hadoopConfiguration): Unit
saveAsNewAPIHadoopFile用于将RDD数据保存到HDFS上,使用新版本Hadoop API。
用法基本同saveAsHadoopFile。
import org.apache.spark.SparkConf import org.apache.spark.SparkContext import SparkContext._ import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat import org.apache.hadoop.io.Text import org.apache.hadoop.io.IntWritable var rdd1 = sc.makeRDD(Array(("A",2),("A",1),("B",6),("B",3),("B",7))) rdd1.saveAsNewAPIHadoopFile("/tmp/lxw1234/",classOf[Text],classOf[IntWritable],classOf[TextOutputFormat[Text,IntWritable]])123456789
def saveAsNewAPIHadoopDataset(conf: Configuration): Unit
作用同saveAsHadoopDataset,只不过采用新版本Hadoop API。
以写入HBase为例:
HBase建表:
create ‘lxw1234′,{NAME => ‘f1′,VERSIONS => 1},{NAME => ‘f2′,VERSIONS => 1},{NAME => ‘f3′,VERSIONS => 1}
完整的Spark应用程序:
package com.lxw1234.test import org.apache.spark.SparkConf import org.apache.spark.SparkContext import SparkContext._ import org.apache.hadoop.hbase.HBaseConfiguration import org.apache.hadoop.mapreduce.Job import org.apache.hadoop.hbase.mapreduce.TableOutputFormat import org.apache.hadoop.hbase.io.ImmutableBytesWritable import org.apache.hadoop.hbase.client.Result import org.apache.hadoop.hbase.util.Bytes import org.apache.hadoop.hbase.client.Put object Test { def main(args : Array[String]) { val sparkConf = new SparkConf().setMaster("spark://lxw1234.com:7077").setAppName("lxw1234.com") val sc = new SparkContext(sparkConf); var rdd1 = sc.makeRDD(Array(("A",2),("B",6),("C",7))) sc.hadoopConfiguration.set("hbase.zookeeper.quorum ","zkNode1,zkNode2,zkNode3") sc.hadoopConfiguration.set("zookeeper.znode.parent","/hbase") sc.hadoopConfiguration.set(TableOutputFormat.OUTPUT_TABLE,"lxw1234") var job = new Job(sc.hadoopConfiguration) job.setOutputKeyClass(classOf[ImmutableBytesWritable]) job.setOutputValueClass(classOf[Result]) job.setOutputFormatClass(classOf[TableOutputFormat[ImmutableBytesWritable]]) rdd1.map( x => { var put = new Put(Bytes.toBytes(x._1)) put.add(Bytes.toBytes("f1"), Bytes.toBytes("c1"), Bytes.toBytes(x._2)) (new ImmutableBytesWritable,put) } ).saveAsNewAPIHadoopDataset(job.getConfiguration) sc.stop() } } 123456789101112131415161718192021222324252627282930313233343536373839
注意:保存到HBase,运行时候需要在SPARK_CLASSPATH中加入HBase相关的jar包。
可参考:http://lxw1234.com/archives/2015/07/332.htm
参考文献
https://blog.csdn.net/lemonZhaoTao/article/details/77923337
https://techvidvan.com/tutorials/ways-to-create-rdd-in-spark/
https://data-flair.training/blogs/create-rdds-in-apache-spark/
https://blog.csdn.net/legotime/article/details/51871724