Spark开发小笔记:从0开始的Spark建图生活
持续更新中……
支持多种语言,默认是scala(背后是spark shell),SparkSQL, Markdown 和 Shell。
是一个基于web的笔记本,支持交互式数据分析。你可以用SQL、Scala等做出数据驱动的、交互、协作的文档。
Spark里的计算都是操作RDD进行,那么学习RDD的第一个问题就是如何构建RDD,构建RDD从数据来源角度分为两类:第一类是从内存里直接读取数据,第二类就是从文件系统里读取,当然这里的文件系统种类很多常见的就是HDFS以及本地文件系统了。
/* 使用makeRDD创建RDD */
val rdd01 = sc.makeRDD(List(1,2,3,4,5,6))
val r01 = rdd01.map { x => x * x }
println(r01.collect().mkString(","))
/*通过文件系统构造RDD*/
val rdd:RDD[String] = sc.textFile("file:///D:/sparkdata.txt", 1)
val r:RDD[String] = rdd.flatMap { x => x.split(",") }
println(r.collect().mkString(","))
/*通过调用SparkContext的parallelize方法,
在一个已经存在的Scala集合上创建的(一个Seq对象)。
集合的对象将会被拷贝,创建出一个可以被并行操作的分布式数据集。*/
data = [1, 2, 3, 4, 5]
distData = sc.parallelize(data)
/*一旦分布式数据集(distData)被创建好,它们将可以被并行操作。*/
/*并行集合的一个重要参数是slices,表示数据集切分的份数。
Spark将会在集群上为每一份数据起一个任务。典型地,你可以
在集群的每个CPU上分布2-4个slices. 一般来说,Spark会尝
试根据集群的状况,来自动设定slices的数目。然而,你也可以
通过传递给parallelize的第二个参数来进行手动设置。*/
RDD的操作分为转化操作(transformation)和行动操作(action),RDD之所以将操作分成这两类这是和RDD惰性运算有关,当RDD执行转化操作时候,实际计算并没有被执行,只有当RDD执行行动操作时候才会促发计算任务提交,执行相应的计算操作。区别转化操作和行动操作也非常简单,转化操作就是从一个RDD产生一个新的RDD操作,而行动操作就是进行实际的计算。
Hive + sql(数据类型) + 参数配置(SparkConf, SparkContext)
import org.apache.spark.sql.hive.HiveContext
import org.apache.spark.sql._
import org.apache.spark.{SparkConf, SparkContext}
package定义: 文件顶部package定义
package com.sunny.scala.service
package特性:
object GenGraph {
def main(args: Array[String]) {
/** 初始化配置 */
val conf = new SparkConf().setAppName("example")
val sc = new SparkContext(conf)
val sqlContext = new HiveContext(sc)
val vertexSchema = new StructType()
.add("p_id", LongType)
.add("label_id", LongType)
import scala.collection.mutable.HashMap
/**Initializing*/
/**3元素法*/
val hashMap1: HashMap[String, String] = HashMap(("PD","Plain Donut"),("SD","Strawberry Donut"),("CD","Chocolate Donut"))
/**key->value法*/
val hashMap2: HashMap[String, String] = HashMap("VD"-> "Vanilla Donut", "GD" -> "Glazed Donut")
/**EMPTY ONE*/
val emptyMap: HashMap[String,String] = HashMap.empty[String,String]
/**Access*/
println(s"Element by key VD = ${hashMap2("VD")}")
hashMap1 += ("KD" -> "Krispy Kreme Donut")
hashMap1 -= "CD"/**加减元素*/
hashMap1 ++= hashMap2 /**一个hashMap加到另一个上*/
/**规范化写法,scala函数的返回值是最后一行代码*/
def addInt(a:Int,b:Int) : Int = {
var total : Int = a + b
return total
}
/**Unit,是Scala语言中数据类型的一种,表示无值,用作不返回任何结果的方法*/
def returnUnit(): Unit = {
println("shows!")
}
/**不写明返回值的类型,程序会自行判断,最后一行代码的执行结果为返回值*/
def addInt(a:Int,b:Int) = {
a+b
}
/**只有一行的写法*/
def addInt (a:Int,b:Int) = x + y
/**最简单写法:def ,{ },返回值都可以省略,此方法在spark编程中经常使用。*/
val addInt = (x: Int,y: Int) => x + y
for循环:是不断的循环一个集合,然后for循环后面的{}代码块部分会根据for循环()里面提取的集合的item来作为{}的输入进行流程控制。
for(i<-0 to 5 if i==5){
println(i)
}
val sql=
"""
|select user_id,
| user_type,
| city_id,
| bank_type,
| edge_weight,
| edge_count
| from $sourceTable
| where datekey = $datekey
""".stripMargin
match到的case即进行对应case的操作:
def getSalary(name:String,age:Int){
name match{
//从前往后匹配
case "Spark"=>println("$150000/year")
case "Hadoop"=>println("$100000/year")
//加入判断条件(用变量接受参数)
case _name if age>=5 =>println(name+":"+age+" $140000/year")
case _ =>println("$90000/year")//都不匹配时
}
}
//对类型进行匹配
def getMatchType(msg:Any){
msg match{
case i : Int=>println("Integer")
case s : String=>println("String")
case d : Double=>println("Double")
case _=>println("Unknow type")
}
}
map()将原数据的每个元素传给函数func进行格式化,返回一个新的分布式数据集。
flatMap(func)跟map(func)类似,但是每个输入项可成为0个或多个输出项(所以func函数应该返回的是一个序列化的数据而不是单个数据项)。flatMap(func)也会对每一条输入进行执行的func操作,然后每一条输入返回一个对象,但是最后会将所有的对象再合成为一个对象。
map返回的数据对象的个数和原来的输入数据是相同的,而flatMap返回的个数则是不同的。
var mapResult = textFile.map(line => line.split("\\s+"))
import java.text.SimpleDateFormat
val simpleDateFormat = new SimpleDateFormat("yyyyMMdd")
val date = simpleDateFormat.parse(datekey);
*容易踩坑注目
Q1:Date formats 是线程不安全的。推荐为每个线程创建单独的format实例。如果多线程并发访问同一个format实例,必须加同步操作,正确写法如下:
class DateUtils {
public static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
private static final Object LOCK = new Object();
// OK
public Date parseString(String datetime) throws Exception {
synchronized (LOCK) {
return format.parse(datetime);
}
}
// OK
public Date parseStringV2(String datetime) throws Exception {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
return format.parse(datetime);
}
}
Q2:在很多时候程序使用SimpleDateFormat都能正常执行,并不会报错;但有时发现日志出现java.text.ParseException: Unparseable date: "2017-03-20 02:10"异常,为什么还抛出这种异常呢?
当使用format方法将Date转成String时,SimpleDateFormat可实例化为任意期望的时间格式;但是使用parse方法将String转为Date时,SimpleDateFormat定义的格式与参数String的格式必须完全一致,不然就会出现Unparseable date。
字符串插值允许使用者将变量引用直接插入处理过的字面字符中。编译器会对它做额外的工作。待处理字符串字面通过“号前的字符来标示。
def getHeterEdgeSql(sourceTable:String, datekey: Int) :String=
s"""
|select user_id,
| from $sourceTable
| where datekey = $datekey
""".stripMargin
Scala 提供了三种创新的字符串插值方法:s,f 和 raw:
println(s"1+1=${1+1}") /*将会输出字符串1+1=2。*/
val height=1.9d
val name="James"
println(f"$name%s is $height%2.2f meters tall")
/*James is 1.90 meters tall f 插值器是类型安全的。*/
/*如果试图向只支持 int 的格式化串传入一个double 值,编译器则会报错.*/
val height:Double=1.9d
s"a\nb"
res0:String=
a
b
/*这里,s 插值器用回车代替了\n。而raw插值器却不会如此处理。*/
raw"a\nb"
res1:String=a\nb /*当不想输入\n被转换为回车的时候,raw 插值器是非常实用的。*/
GraphX的核心抽象是Resilient Distributed Property Graph,一种点和边都带属性的有向多重图。它扩展了Spark RDD的抽象,有Table和Graph两种视图,而只需要一份物理存储。两种视图都有自己独有的操作符,从而获得了灵活操作和执行效率。
对Graph视图的所有操作,最终都会转换成其关联的Table视图的RDD操作来完成。这样对一个图的计算,最终在逻辑上,等价于一系列RDD的转换过程。因此,Graph最终具备了RDD的3个关键特性:Immutable、Distributed和Fault-Tolerant,其中最关键的是Immutable(不变性)。逻辑上,所有图的转换和操作都产生了一个新图;物理上,GraphX会有一定程度的不变顶点和边的复用优化,对用户透明。
override是覆盖的意思,在很多语言中都有,在scala中,override是非常常见的。
当一个类extends另外一个类的时候,override的规则基本如下:
子类中的方法要覆盖父类中的方法,必须写override。
子类中的属性val要覆盖父类中的属性,必须写override。
父类中的变量不可以覆盖。