spark周边

一、shark

Shark自己也没用过,不太熟悉,只了解它的背景,现在已经被Spark淘汰,也不去熟悉它了!

Spark 1.0版本开始,推出了Spark SQL。

其实最早使用的,都是Hadoop自己的Hive查询引擎;但是后来Spark提供了Shark;再后来Shark被淘汰,推出了Spark SQL。Shark的性能比Hive就要高出一个数量级,而Spark SQL的性能又比Shark高出一个数量级。

最早来说,Hive的诞生,主要是因为要让那些不熟悉Java,无法深入进行MapReduce编程的数据分析师,能够使用他们熟悉的关系型数据库的SQL模型,来操作HDFS上的数据。因此推出了Hive。Hive底层基于MapReduce实现SQL功能,能够让数据分析人员,以及数据开发人员,方便的使用Hive进行数据仓库的建模和建设,然后使用SQL模型针对数据仓库中的数据进行统计和分析。但是Hive有个致命的缺陷,就是它的底层基于MapReduce,而MapReduce的shuffle又是基于磁盘的,因此导致Hive的性能异常低下。进场出现复杂的SQL ETL,要运行数个小时,甚至数十个小时的情况。

后来,Spark推出了Shark,Shark与Hive实际上还是紧密关联的,Shark底层很多东西还是依赖于Hive,但是修改了内存管理、物理计划、执行三个模块,底层使用Spark的基于内存的计算模型,从而让性能比Hive提升了数倍到上百倍。

然而,Shark还是它的问题所在,Shark底层依赖了Hive的语法解析器、查询优化器等组件,因此对于其性能的提升还是造成了制约。所以后来Spark团队决定,完全抛弃Shark,推出了全新的Spark SQL项目。Spark SQL就不只是针对Hive中的数据了,而且可以支持其他很多数据源的查询。

二、aparkR

1. sparkR的简介

SparkR是一个R语言包,它提供了轻量级的方式使得可以在R语言中使用Apache Spark。在Spark 1.4中,SparkR实现了分布式的data frame,支持类似查询、过滤以及聚合的操作(类似于R中的data frames:dplyr),但是这个可以操作大规模的数据集。

2. 使用spark的两种方式

1.在sparkR的shell中交互式使用

sparkR
2.在R脚本中使用

if (nchar(Sys.getenv("SPARK_HOME")) < 1) {
  Sys.setenv(SPARK_HOME = "/home/spark")
}
library(SparkR, lib.loc = c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib")))
sc <- sparkR.init(master = "spark://10.137",sparkEnvir = list(spark.driver.memory="3g"))

3. 纯R语言和SparkR

当数据量很大时,纯R速度就比较慢,无法用到大数据分布式性能,SparkR可以。

注意比较python,pyspark,SparkR等

4. SparkR DataFrame的基本使用

DataFrame是数据组织成一个带有列名称的分布式数据集。在概念上和关系型数据库中的表类似,或者和R语言中的data frame类似,但是这个提供了很多的优化措施。构造DataFrame的方式有很多:可以通过结构化文件中构造;可以通过Hive中的表构造;可以通过外部数据库构造或者是通过现有R的data.frame构造等等。

1.从SparkContext和SQLContext开始

SparkContext是SparkR的切入点,它使得你的R程序和Spark集群互通。你可以通过sparkR.init来构建SparkContext,然后可以传入类似于应用程序名称的选项给它。如果想使用DataFrames,我们得创建SQLContext,这个可以通过SparkContext来构造。如果你使用SparkR shell, SQLContext 和SparkContext会自动地构建好。

sc <- sparkR.init()
sqlContext <- sparkRSQL.init(sc)

2.创建DataFrame

如果有SQLContext实例,那么应用程序就可以通过本地的R data frame(或者是Hive表;或者是其他数据源)来创建DataFrames。下面将详细地介绍。

(1)通过本地data.frame构造

最简单地创建DataFrames是将R的data frame转换成SparkR DataFrames,我们可以通过createDataFrame来创建,并传入本地R的data.frame以此来创建SparkR DataFrames,下面例子就是这种方法:

user=data.frame(name=c('zhangsan','lisi','wangwu','zhaoliu'),age=c(21,23,20,27))
df <- createDataFrame(sqlContext, user)
(2)通过Data Sources构造

通过DataFrame接口,SparkR支持操作多种数据源,本节将介绍如何通过Data Sources提供的方法来加载和保存数据。你可以阅读Spark SQL编程指南来了解更多的options选项.
Data Sources中创建DataFrames的一般方法是使用read.df,这个方法需要传入SQLContext,需要加载的文件路径以及数据源的类型。SparkR内置支持读取JSON和Parquet文件,而且通过Spark Packages你可以读取很多类型的数据,比如CSV和Avro文件。
下面是介绍如何JSON文件,注意,这里使用的文件不是典型的JSON文件。每行文件必须包含一个分隔符、自包含有效的JSON对象:

people <- read.df(sqlContext, "/wmf/people.json", "json")
head(people)
 
# SparkR 能自动从Json文件推断schema
printSchema(people)

Data sources API还可以将DataFrames保存成多种的文件格式,比如我们可以通过write.df将上面的DataFrame保存成Parquet文件:

write.df(people, path="people.parquet", source="parquet", mode="overwrite")
(3)通过Hive tables构造

我们也可以通过Hive表来创建SparkR DataFrames,为了达到这个目的,我们需要创建HiveContext,因为我们可以通过它来访问Hive MetaStore中的表。注意,Spark内置就对Hive提供了支持。

hiveContext <- sparkRHive.init(sc)
sql="能在bdcmagic上运行的sql语句"
results<-sql(hiveContext, sql)
head(results)

3.DataFrame的相关操作

(1)选择行和列
#创建一个数据框
user=data.frame(name=c('zhangsan','lisi','wangwu','zhaoliu'),age=c(21,23,20,27))
df <- createDataFrame(sqlContext, user)
#获得数据框的一个基本信息
df
#选择某一列
head(select(df,df$name)) #或者直接使用数据框的列名来选择head(select(df,name))
(2)Grouping和Aggregation
#n操作符其实就是count的意思
head(summarize(groupBy(df, df$sex), count = n(df$sex)))

#数据框的排序
sex_counts=summarize(groupBy(df, df$sex), count = n(df$sex))
head(arrange(sex_counts, desc(sex_counts$count)))
(3)列上面的操作
SparkR提供了大量的函数用于直接对列进行数据处理的操作。
#为数据框增加一列
df$second_age=df$age+10
head(df)
(4)在数据框上使用SQL查询
#创建一个数据框
...

#将数据框注册成表
registerTempTable(df, "people")

#运行sql语句
sql(hiveContext,"sql语句,eg:select * from people")

#过滤,选择满足条件的行
head(filter(df, df$age < 23))

三、spark streaming

1. Spark Streaming介绍

Spark Streaming 是Spark核心API的一个扩展,可以实现高吞吐量的、具备容错机制的实时流数据的处理。支持从多种数据源获取数据,包括Kafk、Flume、Twitter、ZeroMQ、Kinesis 以及TCP sockets,从数据源获取数据之后,可以使用诸如map、reduce、join和window等高级函数进行复杂算法的处理。最后还可以将处理结果存储到文件系统,数据库和现场仪表盘。在“One Stack rule them all”的基础上,还可以使用Spark的其他子框架,如集群学习、图计算等,对流数据进行处理。

Spark Streaming处理的数据流图:
spark周边_第1张图片

Spark的各个子框架,都是基于核心Spark的,Spark Streaming在内部的处理机制是,接收实时流的数据,并根据一定的时间间隔拆分成一批批的数据,然后通过Spark Engine处理这些批数据,最终得到处理后的一批批结果数据。

对应的批数据,在Spark内核对应一个RDD实例,因此,对应流数据的DStream可以看成是一组RDDs,即RDD的一个序列。通俗点理解的话,在流数据分成一批一批后,通过一个先进先出的队列,然后 Spark Engine从该队列中依次取出一个个批数据,把批数据封装成一个RDD,然后进行处理,这是一个典型的生产者消费者模型,对应的就有生产者消费者模型的问题,即如何协调生产速率和消费速率。

2. Storm与Spark Streming比较

1、处理模型以及延迟
虽然两框架都提供了可扩展性(scalability)和可容错性(fault tolerance),但是它们的处理模型从根本上说是不一样的。Storm可以实现亚秒级时延的处理,而每次只处理一条event,而Spark Streaming可以在一个短暂的时间窗口里面处理多条(batches)Event。所以说Storm可以实现亚秒级时延的处理,而Spark Streaming则有一定的时延。
2、容错和数据保证
然而两者的代价都是容错时候的数据保证,Spark Streaming的容错为有状态的计算提供了更好的支持。在Storm中,每条记录在系统的移动过程中都需要被标记跟踪,所以Storm只能保证每条记录最少被处理一次,但是允许从错误状态恢复时被处理多次。这就意味着可变更的状态可能被更新两次从而导致结果不正确。
另一方面,Spark Streaming仅仅需要在批处理级别对记录进行追踪,所以他能保证每个批处理记录仅仅被处理一次,即使是node节点挂掉。虽然说Storm的 Trident library可以保证一条记录被处理一次,但是它依赖于事务更新状态,而这个过程是很慢的,并且需要由用户去实现。
3、实现和编程API
Storm主要是由Clojure语言实现,Spark Streaming是由Scala实现。如果你想看看这两个框架是如何实现的或者你想自定义一些东西你就得记住这一点。Storm是由BackType和 Twitter开发,而Spark Streaming是在UC Berkeley开发的。
Storm提供了Java API,同时也支持其他语言的API。 Spark Streaming支持Scala和Java语言(其实也支持Python)。
4、批处理框架集成
Spark Streaming的一个很棒的特性就是它是在Spark框架上运行的。这样你就可以想使用其他批处理代码一样来写Spark Streaming程序,或者是在Spark中交互查询。这就减少了单独编写流批量处理程序和历史数据处理程序。
5、生产支持
Storm已经出现好多年了,而且自从2011年开始就在Twitter内部生产环境中使用,还有其他一些公司。而Spark Streaming是一个新的项目,并且在2013年仅仅被Sharethrough使用(据作者了解)。
Storm是 Hortonworks Hadoop数据平台中流处理的解决方案,而Spark Streaming出现在 MapR的分布式平台和Cloudera的企业数据平台中。除此之外,Databricks是为Spark提供技术支持的公司,包括了Spark Streaming。
虽然说两者都可以在各自的集群框架中运行,但是Storm可以在Mesos上运行, 而Spark Streaming可以在YARN和Mesos上运行。

3. Spark Streaming架构

SparkStreaming是一个对实时数据流进行高通量、容错处理的流式处理系统,可以对多种数据源(如Kdfka、Flume、Twitter、Zero和TCP 套接字)进行类似Map、Reduce和Join等复杂操作,并将结果保存到外部文件系统、数据库或应用到实时仪表盘。

1、计算流程:
Spark Streaming是将流式计算分解成一系列短小的批处理作业。这里的批处理引擎是Spark Core,也就是把Spark Streaming的输入数据按照batch size(如1秒)分成一段一段的数据(Discretized Stream),每一段数据都转换成Spark中的RDD(Resilient Distributed Dataset),然后将Spark Streaming中对DStream的Transformation操作变为针对Spark中对RDD的Transformation操作,将RDD经过操作变成中间结果保存在内存中。整个流式计算根据业务的需求可以对中间的结果进行叠加或者存储到外部设备。下图显示了Spark Streaming的整个流程。
spark周边_第2张图片
图Spark Streaming构架
2、容错性:
对于流式计算来说,容错性至关重要。首先我们要明确一下Spark中RDD的容错机制。每一个RDD都是一个不可变的分布式可重算的数据集,其记录着确定性的操作继承关系(lineage),所以只要输入数据是可容错的,那么任意一个RDD的分区(Partition)出错或不可用,都是可以利用原始输入数据通过转换操作而重新算出的。
对于Spark Streaming来说,其RDD的传承关系如下图所示,图中的每一个椭圆形表示一个RDD,椭圆形中的每个圆形代表一个RDD中的一个Partition,图中的每一列的多个RDD表示一个DStream(图中有三个DStream),而每一行最后一个RDD则表示每一个Batch Size所产生的中间结果RDD。我们可以看到图中的每一个RDD都是通过lineage相连接的,由于Spark Streaming输入数据可以来自于磁盘,例如HDFS(多份拷贝)或是来自于网络的数据流(Spark Streaming会将网络输入数据的每一个数据流拷贝两份到其他的机器)都能保证容错性,所以RDD中任意的Partition出错,都可以并行地在其他机器上将缺失的Partition计算出来。这个容错恢复方式比连续计算模型(如Storm)的效率更高。
spark周边_第3张图片
Spark Streaming中RDD的lineage关系图
3、实时性:
对于实时性的讨论,会牵涉到流式处理框架的应用场景。Spark Streaming将流式计算分解成多个Spark Job,对于每一段数据的处理都会经过Spark DAG图分解以及Spark的任务集的调度过程。对于目前版本的Spark Streaming而言,其最小的Batch Size的选取在0.5~2秒钟之间(Storm目前最小的延迟是100ms左右),所以Spark Streaming能够满足除对实时性要求非常高(如高频实时交易)之外的所有流式准实时计算场景。
4、扩展性与吞吐量:
Spark目前在EC2上已能够线性扩展到100个节点(每个节点4Core),可以以数秒的延迟处理6GB/s的数据量(60M records/s),其吞吐量也比流行的Storm高2~5倍,图4是Berkeley利用WordCount和Grep两个用例所做的测试,在Grep这个测试中,Spark Streaming中的每个节点的吞吐量是670k records/s,而Storm是115k records/s。
spark周边_第4张图片
Spark Streaming与Storm吞吐量比较图

4. 操作示例

需要统计接收的文本数据中的每个单词的出现频率:

首先,我们将Spark Streaming相关的类和StreamingContext的一些隐式转换导入到我们的环境中,以便为我们需要的其他类(如DStream)添加有用的方法。StreamingContext是所有流功能的主要入口点。我们创建一个带有两个执行线程(译者注:如果要执行本例,必须确保机器cpu核心大于2)的本地StreamingContext,并且设置流数据每批的间隔为1秒。

import org.apache.spark._  
import org.apache.spark.streaming._  
import org.apache.spark.streaming.StreamingContext._ // not necessary since Spark 1.3  
// Create a local StreamingContext with two working thread and batch interval of 1 second.  
// The master requires 2 cores to prevent from a starvation scenario.  
val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")  
val ssc = new StreamingContext(conf, Seconds(1))  

使用此context,我们可以创建一个DStream,它表示来自特定主机名(例如localhost)和端口(例如9999)TCP源的流数据。

// Create a DStream that will connect to hostname:port, like localhost:9999  
val lines = ssc.socketTextStream("localhost", 9999)  

在这行代码中,DStream表示从数据服务器接收的数据流。此DStream中的每个记录都是一行文本。接下来,我们要将每行文本以空格符为分隔符切分成一个个单词。

// Split each line into words  
val words = lines.flatMap(_.split(" "))  

flatMap是一个一对多的DStream操作,该操作通过从源DStream中的每个记录生成多个新记录来创建新的DStream。在这种情况下,每一行将被分割成多个单词,并将单词流表示为单词DStream。接下来,我们对这些单词进行计数。

import org.apache.spark.streaming.StreamingContext._ // not necessary since Spark 1.3  
// Count each word in each batch  
val pairs = words.map(word => (word, 1))  
val wordCounts = pairs.reduceByKey(_ + _)  
// Print the first ten elements of each RDD generated in this DStream to the console  
wordCounts.print()  

单词DStream进一步映射(一对一变换)到(word,1) 键值对的DStream,然后进行聚合以获得每批数据中的单词的频率。最后,wordCounts.print()将打印每秒产生的计数结果中的若干条记录。

请注意,当执行这些代码时,Spark Streaming仅是设置了预计算流程,目前为止这些计算还没有真正的开始执行。在设置好所有计算操作后,要开始真正的执行过程,我们最终需要调用如下方法:

ssc.start()             // Start the computation  
ssc.awaitTermination()  // Wait for the computation to terminate  

完整的代码可以在SparkStreaming示例NetworkWordCount中找到。

如果您已经下载并构建了Spark,则可以以下面的方式运行此示例。在运行spark程序之前您将首先需要运行Netcat(大多数类Unix系统中的一个小型实用程序)作为数据服务器。

$ nc -lk 9999
然后,打开另外一个终端,键入一下命令启动示例
$ ./bin/run-example streaming.NetworkWordCount localhost 9999
然后,在运行netcat服务器的终端中输入的任何行将每秒进行单词计数并打印在屏幕上。 

完整代码:

package org.apache.spark.streaming.examples  
  
import org.apache.spark.streaming.{Seconds, StreamingContext}  
import org.apache.spark.streaming.StreamingContext._  
import org.apache.spark.storage.StorageLevel  
  
object NetworkWordCount {  
  def main(args: Array[String]) {  
    if (args.length < 3) {  
      System.err.println("Usage: NetworkWordCount   \n" +  
        "In local mode,  should be 'local[n]' with n > 1")  
      System.exit(1)  
    }  
  
    StreamingExamples.setStreamingLogLevels()  
  
    // Create the context with a 1 second batch size  
    val ssc = new StreamingContext(args(0), "NetworkWordCount", Seconds(1),  
      System.getenv("SPARK_HOME"), StreamingContext.jarOfClass(this.getClass))  
  
    // Create a NetworkInputDStream on target ip:port and count the  
    // words in input stream of \n delimited text (eg. generated by 'nc')  
    val lines = ssc.socketTextStream(args(1), args(2).toInt, StorageLevel.MEMORY_ONLY_SER)  
    val words = lines.flatMap(_.split(" "))  
    val wordCounts = words.map(x => (x, 1)).reduceByKey(_ + _)  
    wordCounts.print()  
    ssc.start()  
    ssc.awaitTermination()  
  }  
}  

四、Spark GraphX

1. Spark GraphX应用背景

Spark GraphX是一个分布式图处理框架,它是基于Spark平台提供对图计算和图挖掘简洁易用的而丰富的接口,极大的方便了对分布式图处理的需求。

众所周知,社交网络中人与人之间有很多关系链,例如Twitter、Facebook、微博和微信等,这些都是大数据产生的地方都需要图计算,现在的图处理基本都是分布式的图处理,而并非单机处理。Spark GraphX由于底层是基于Spark来处理的,所以天然就是一个分布式的图处理系统。

图的分布式或者并行处理其实是把图拆分成很多的子图,然后分别对这些子图进行计算,计算的时候可以分别迭代进行分阶段的计算,即对图进行并行计算。下面我们看一下图计算的简单示例:

spark周边_第5张图片

从图中我们可以看出:拿到Wikipedia的文档以后,可以变成Link Table形式的视图,然后基于Link Table形式的视图可以分析成Hyperlinks超链接,最后我们可以使用PageRank去分析得出Top Communities。在下面路径中的Editor Graph到Community,这个过程可以称之为Triangle Computation,这是计算三角形的一个算法,基于此会发现一个社区。从上面的分析中我们可以发现图计算有很多的做法和算法,同时也发现图和表格可以做互相的转换。

2. Spark GraphX的框架

设计GraphX时,点分割和GAS都已成熟,在设计和编码中针对它们进行了优化,并在功能和性能之间寻找最佳的平衡点。如同Spark本身,每个子模块都有一个核心抽象。GraphX的核心抽象是Resilient Distributed Property Graph,一种点和边都带属性的有向多重图。它扩展了Spark RDD的抽象,有Table和Graph两种视图,而只需要一份物理存储。两种视图都有自己独有的操作符,从而获得了灵活操作和执行效率。

spark周边_第6张图片

如同Spark,GraphX的代码非常简洁。GraphX的核心代码只有3千多行,而在此之上实现的Pregel模式,只要短短的20多行。GraphX的代码结构整体下图所示,其中大部分的实现,都是围绕Partition的优化进行的。这在某种程度上说明了点分割的存储和相应的计算优化,的确是图计算框架的重点和难点。

3. 图例演示

下图中有6个人,每个人有名字和年龄,这些人根据社会关系形成8条边,每条边有其属性。在以下例子演示中将构建顶点、边和图,打印图的属性、转换操作、结构操作、连接操作、聚合操作,并结合实际要求进行演示。

spark周边_第7张图片

程序代码:

import org.apache.log4j.{Level, Logger}
import org.apache.spark.{SparkContext, SparkConf}
import org.apache.spark.graphx._
import org.apache.spark.rdd.RDD

object GraphXExample {
  def main(args: Array[String]) {
    //屏蔽日志
    Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
    Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)

    //设置运行环境
    val conf = new SparkConf().setAppName("SimpleGraphX").setMaster("local")
    val sc = new SparkContext(conf)

    //设置顶点和边,注意顶点和边都是用元组定义的Array
    //顶点的数据类型是VD:(String,Int)
    val vertexArray = Array(
      (1L, ("Alice", 28)),
      (2L, ("Bob", 27)),
      (3L, ("Charlie", 65)),
      (4L, ("David", 42)),
      (5L, ("Ed", 55)),
      (6L, ("Fran", 50))
    )
    //边的数据类型ED:Int
    val edgeArray = Array(
      Edge(2L, 1L, 7),
      Edge(2L, 4L, 2),
      Edge(3L, 2L, 4),
      Edge(3L, 6L, 3),
      Edge(4L, 1L, 1),
      Edge(5L, 2L, 2),
      Edge(5L, 3L, 8),
      Edge(5L, 6L, 3)
    )

    //构造vertexRDD和edgeRDD
    val vertexRDD: RDD[(Long, (String, Int))] = sc.parallelize(vertexArray)
    val edgeRDD: RDD[Edge[Int]] = sc.parallelize(edgeArray)

    //构造图Graph[VD,ED]
    val graph: Graph[(String, Int), Int] = Graph(vertexRDD, edgeRDD)

    //***********************************************************************************
    //***************************  图的属性    ****************************************
    //**********************************************************************************    println("***********************************************")
    println("属性演示")
    println("**********************************************************")
    println("找出图中年龄大于30的顶点:")
    graph.vertices.filter { case (id, (name, age)) => age > 30}.collect.foreach {
      case (id, (name, age)) => println(s"$name is $age")
    }

    //边操作:找出图中属性大于5的边
    println("找出图中属性大于5的边:")
graph.edges.filter(e => e.attr > 5).collect.foreach(e => println(s"${e.srcId} to ${e.dstId} att ${e.attr}"))
    println

    //triplets操作,((srcId, srcAttr), (dstId, dstAttr), attr)
    println("列出边属性>5的tripltes:")
    for (triplet <- graph.triplets.filter(t => t.attr > 5).collect) {
      println(s"${triplet.srcAttr._1} likes ${triplet.dstAttr._1}")
    }
    println

    //Degrees操作
    println("找出图中最大的出度、入度、度数:")
    def max(a: (VertexId, Int), b: (VertexId, Int)): (VertexId, Int) = {
      if (a._2 > b._2) a else b
    }
    println("max of outDegrees:" + graph.outDegrees.reduce(max) + " max of inDegrees:" + graph.inDegrees.reduce(max) + " max of Degrees:" + graph.degrees.reduce(max))
    println

    //***********************************************************************************
    //***************************  转换操作    ****************************************
    //**********************************************************************************   
    println("**********************************************************")
    println("转换操作")
    println("**********************************************************")
    println("顶点的转换操作,顶点age + 10:")
    graph.mapVertices{ case (id, (name, age)) => (id, (name, age+10))}.vertices.collect.foreach(v => println(s"${v._2._1} is ${v._2._2}"))
    println
    println("边的转换操作,边的属性*2:")
    graph.mapEdges(e=>e.attr*2).edges.collect.foreach(e => println(s"${e.srcId} to ${e.dstId} att ${e.attr}"))
    println

    //***********************************************************************************
    //***************************  结构操作    ****************************************
    //**********************************************************************************  
    println("**********************************************************")
    println("结构操作")
    println("**********************************************************")
    println("顶点年纪>30的子图:")
    val subGraph = graph.subgraph(vpred = (id, vd) => vd._2 >= 30)
    println("子图所有顶点:")
    subGraph.vertices.collect.foreach(v => println(s"${v._2._1} is ${v._2._2}"))
    println
    println("子图所有边:")
    subGraph.edges.collect.foreach(e => println(s"${e.srcId} to ${e.dstId} att ${e.attr}"))
    println


    //***********************************************************************************
    //***************************  连接操作    ****************************************
    //**********************************************************************************  
    println("**********************************************************")
    println("连接操作")
    println("**********************************************************")
    val inDegrees: VertexRDD[Int] = graph.inDegrees
    case class User(name: String, age: Int, inDeg: Int, outDeg: Int)

    //创建一个新图,顶点VD的数据类型为User,并从graph做类型转换
    val initialUserGraph: Graph[User, Int] = graph.mapVertices { case (id, (name, age)) => User(name, age, 0, 0)}

    //initialUserGraph与inDegrees、outDegrees(RDD)进行连接,并修改initialUserGraph中inDeg值、outDeg值
    val userGraph = initialUserGraph.outerJoinVertices(initialUserGraph.inDegrees) {
      case (id, u, inDegOpt) => User(u.name, u.age, inDegOpt.getOrElse(0), u.outDeg)
    }.outerJoinVertices(initialUserGraph.outDegrees) {
      case (id, u, outDegOpt) => User(u.name, u.age, u.inDeg,outDegOpt.getOrElse(0))
    }

    println("连接图的属性:")
userGraph.vertices.collect.foreach(v => println(s"${v._2.name} inDeg: ${v._2.inDeg}  outDeg: ${v._2.outDeg}"))
    println

    println("出度和入读相同的人员:")
    userGraph.vertices.filter {
      case (id, u) => u.inDeg == u.outDeg
    }.collect.foreach {
      case (id, property) => println(property.name)
    }
    println

    //***********************************************************************************
    //***************************  聚合操作    ****************************************
    //**********************************************************************************  
    println("**********************************************************")
    println("聚合操作")
    println("**********************************************************")
    println("找出年纪最大的追求者:")
    val oldestFollower: VertexRDD[(String, Int)] = userGraph.mapReduceTriplets[(String, Int)](
      // 将源顶点的属性发送给目标顶点,map过程
      edge => Iterator((edge.dstId, (edge.srcAttr.name, edge.srcAttr.age))),
      // 得到最大追求者,reduce过程
      (a, b) => if (a._2 > b._2) a else b
    )

    userGraph.vertices.leftJoin(oldestFollower) { (id, user, optOldestFollower) =>
      optOldestFollower match {
        case None => s"${user.name} does not have any followers."
        case Some((name, age)) => s"${name} is the oldest follower of ${user.name}."
      }
    }.collect.foreach { case (id, str) => println(str)}
    println

     //***********************************************************************************
    //***************************  实用操作    ****************************************
    //**********************************************************************************
    println("**********************************************************")
    println("聚合操作")
    println("**********************************************************")
    println("找出5到各顶点的最短:")
    val sourceId: VertexId = 5L // 定义源点
    val initialGraph = graph.mapVertices((id, _) => if (id == sourceId) 0.0 else Double.PositiveInfinity)
    val sssp = initialGraph.pregel(Double.PositiveInfinity)(
      (id, dist, newDist) => math.min(dist, newDist),
      triplet => {  // 计算权重
        if (triplet.srcAttr + triplet.attr < triplet.dstAttr) {
          Iterator((triplet.dstId, triplet.srcAttr + triplet.attr))
        } else {
          Iterator.empty
        }
      },
      (a,b) => math.min(a,b) // 最短距离
    )
    println(sssp.vertices.collect.mkString("\n"))

    sc.stop()
  }
}

运行结果:

**********************************************************
属性演示
**********************************************************
找出图中年龄大于30的顶点:
David is 42
Fran is 50
Charlie is 65
Ed is 55
找出图中属性大于5的边:
2 to 1 att 7
5 to 3 att 8

列出边属性>5的tripltes:
Bob likes Alice
Ed likes Charlie

找出图中最大的出度、入度、度数:
max of outDegrees:(5,3) max of inDegrees:(2,2) max of Degrees:(2,4)

**********************************************************
转换操作
**********************************************************
顶点的转换操作,顶点age + 10:
4 is (David,52)
1 is (Alice,38)
6 is (Fran,60)
3 is (Charlie,75)
5 is (Ed,65)
2 is (Bob,37)

边的转换操作,边的属性*2:
2 to 1 att 14
2 to 4 att 4
3 to 2 att 8
3 to 6 att 6
4 to 1 att 2
5 to 2 att 4
5 to 3 att 16
5 to 6 att 6

**********************************************************
结构操作
**********************************************************
顶点年纪>30的子图:
子图所有顶点:
David is 42
Fran is 50
Charlie is 65
Ed is 55

子图所有边:
3 to 6 att 3
5 to 3 att 8
5 to 6 att 3

**********************************************************
连接操作
**********************************************************
连接图的属性:
David inDeg: 1  outDeg: 1
Alice inDeg: 2  outDeg: 0
Fran inDeg: 2  outDeg: 0
Charlie inDeg: 1  outDeg: 2
Ed inDeg: 0  outDeg: 3
Bob inDeg: 2  outDeg: 2

出度和入读相同的人员:
David
Bob

**********************************************************
聚合操作
**********************************************************
找出年纪最大的追求者:
Bob is the oldest follower of David.
David is the oldest follower of Alice.
Charlie is the oldest follower of Fran.
Ed is the oldest follower of Charlie.
Ed does not have any followers.
Charlie is the oldest follower of Bob.

**********************************************************
实用操作
**********************************************************
找出5到各顶点的最短:
(4,4.0)
(1,5.0)
(6,3.0)
(3,8.0)
(5,0.0)
(2,2.0)

4. PageRank 演示

PageRank, 即网页排名,又称网页级别、Google 左侧排名或佩奇排名。它是Google 创始人拉里• 佩奇和谢尔盖• 布林于1997 年构建早期的搜索系统原型时提出的链接分析算法。目前很多重要的链接分析算法都是在PageRank 算法基础上衍生出来的。PageRank 是Google 用于用来标识网页的等级/ 重要性的一种方法,是Google 用来衡量一个网站的好坏的唯一标准。在揉合了诸如Title 标识和Keywords 标识等所有其它因素之后, Google 通过PageRank 来调整结果,使那些更具“等级/ 重要性”的网页在搜索结果中令网站排名获得提升,从而提高搜索结果的相关性和质量。

spark周边_第8张图片

测试数据:

在这里测试数据为顶点数据graphx-wiki-vertices.txt和边数据graphx-wiki-edges.txt

顶点为顶点编号和网页标题
:
这里写图片描述

边数据由两个顶点构成
:
这里写图片描述

程序代码:

import org.apache.log4j.{Level, Logger}
import org.apache.spark.{SparkContext, SparkConf}
import org.apache.spark.graphx._
import org.apache.spark.rdd.RDD

object PageRank {
  def main(args: Array[String]) {
    //屏蔽日志
    Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
    Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)

    //设置运行环境
    val conf = new SparkConf().setAppName("PageRank").setMaster("local")
    val sc = new SparkContext(conf)

    //读入数据文件
    val articles: RDD[String] = sc.textFile("/home/hadoop/IdeaProjects/data/graphx/graphx-wiki-vertices.txt")
    val links: RDD[String] = sc.textFile("/home/hadoop/IdeaProjects/data/graphx/graphx-wiki-edges.txt")

    //装载顶点和边
    val vertices = articles.map { line =>
      val fields = line.split('\t')
      (fields(0).toLong, fields(1))
    }

    val edges = links.map { line =>
      val fields = line.split('\t')
      Edge(fields(0).toLong, fields(1).toLong, 0)
    }

    //cache操作
    //val graph = Graph(vertices, edges, "").persist(StorageLevel.MEMORY_ONLY_SER)
    val graph = Graph(vertices, edges, "").persist()
    //graph.unpersistVertices(false)

    //测试
    println("**********************************************************")
    println("获取5个triplet信息")
    println("**********************************************************")
    graph.triplets.take(5).foreach(println(_))

    //pageRank算法里面的时候使用了cache(),故前面persist的时候只能使用MEMORY_ONLY
    println("**********************************************************")
    println("PageRank计算,获取最有价值的数据")
    println("**********************************************************")
    val prGraph = graph.pageRank(0.001).cache()

    val titleAndPrGraph = graph.outerJoinVertices(prGraph.vertices) {
      (v, title, rank) => (rank.getOrElse(0.0), title)
    }

    titleAndPrGraph.vertices.top(10) {
      Ordering.by((entry: (VertexId, (Double, String))) => entry._2._1)
    }.foreach(t => println(t._2._2 + ": " + t._2._1))

    sc.stop()
  }
}

运行结果:

**********************************************************
获取5个triplet信息
**********************************************************
((146271392968588,Computer Consoles Inc.),(7097126743572404313,Berkeley Software Distribution),0)
((146271392968588,Computer Consoles Inc.),(8830299306937918434,University of California, Berkeley),0)
((625290464179456,List of Penguin Classics),(1735121673437871410,George Berkeley),0)
((1342848262636510,List of college swimming and diving teams),(8830299306937918434,University of California, Berkeley),0)
((1889887370673623,Anthony Pawson),(8830299306937918434,University of California, Berkeley),0)

**********************************************************
PageRank计算,获取最有价值的数据
**********************************************************
University of California, Berkeley: 1321.111754312097
Berkeley, California: 664.8841977233583
Uc berkeley: 162.50132743397873
Berkeley Software Distribution: 90.4786038848606
Lawrence Berkeley National Laboratory: 81.90404939641944
George Berkeley: 81.85226118457985
Busby Berkeley: 47.871998218019655
Berkeley Hills: 44.76406979519754
Xander Berkeley: 30.324075347288037
Berkeley County, South Carolina: 28.908336483710308

五、spark mllib

1. 机器学习

机器学习可以看做是一门人工智能的科学,该领域的主要研究对象是人工智能。机器学习利用数据或以往的经验,以此优化计算机程序的性能标准。

spark周边_第9张图片

机器学习强调三个关键词:算法、经验、性能,其处理过程如上图所示。在数据的基础上,通过算法构建出模型并对模型进行评估。评估的性能如果达到要求,就用该模型来测试其他的数据;如果达不到要求,就要调整算法来重新建立模型,再次进行评估。如此循环往复,最终获得满意的经验来处理其他的数据。机器学习技术和方法已经被成功应用到多个领域,比如个性推荐系统,金融反欺诈,语音识别,自然语言处理和机器翻译,模式识别,智能控制等。

2. 基于大数据的机器学习

传统的机器学习算法,由于技术和单机存储的限制,只能在少量数据上使用。即以前的统计/机器学习依赖于数据抽样。但实际过程中样本往往很难做好随机,导致学习的模型不是很准确,在测试数据上的效果也可能不太好。随着 HDFS(Hadoop Distributed File System) 等分布式文件系统出现,存储海量数据已经成为可能。在全量数据上进行机器学习也成为了可能,这顺便也解决了统计随机性的问题。然而,由于 MapReduce 自身的限制,使得使用 MapReduce 来实现分布式机器学习算法非常耗时和消耗磁盘IO。因为通常情况下机器学习算法参数学习的过程都是迭代计算的,即本次计算的结果要作为下一次迭代的输入,这个过程中,如果使用 MapReduce,我们只能把中间结果存储磁盘,然后在下一次计算的时候从新读取,这对于迭代 频发的算法显然是致命的性能瓶颈。

在大数据上进行机器学习,需要处理全量数据并进行大量的迭代计算,这要求机器学习平台具备强大的处理能力。Spark 立足于内存计算,天然的适应于迭代式计算。即便如此,对于普通开发者来说,实现一个分布式机器学习算法仍然是一件极具挑战的事情。幸运的是,Spark提供了一个基于海量数据的机器学习库,它提供了常用机器学习算法的分布式实现,开发者只需要有 Spark 基础并且了解机器学习算法的原理,以及方法相关参数的含义,就可以轻松的通过调用相应的 API 来实现基于海量数据的机器学习过程。其次,Spark-Shell的即席查询也是一个关键。算法工程师可以边写代码边运行,边看结果。spark提供的各种高效的工具正使得机器学习过程更加直观便捷。比如通过sample函数,可以非常方便的进行抽样。当然,Spark发展到后面,拥有了实时批计算,批处理,算法库,SQL、流计算等模块等,基本可以看做是全平台的系统。把机器学习作为一个模块加入到Spark中,也是大势所趋。

3. Spark 机器学习库MLLib

MLlib是Spark的机器学习(Machine Learning)库,旨在简化机器学习的工程实践工作,并方便扩展到更大规模。MLlib由一些通用的学习算法和工具组成,包括分类、回归、聚类、协同过滤、降维等,同时还包括底层的优化原语和高层的管道API。具体来说,其主要包括以下几方面的内容:
算法工具:常用的学习算法,如分类、回归、聚类和协同过滤;
特征化公交:特征提取、转化、降维,和选择公交;
管道(Pipeline):用于构建、评估和调整机器学习管道的工具;
持久性:保存和加载算法,模型和管道;
实用工具:线性代数,统计,数据处理等工具。
Spark 机器学习库从 1.2 版本以后被分为两个包:
spark.mllib包含基于RDD的原始算法API。Spark MLlib 历史比较长,在1.0 以前的版本即已经包含了,提供的算法实现都是基于原始的 RDD。
spark.ml 则提供了基于DataFrames 高层次的API,可以用来构建机器学习工作流(PipeLine)。ML Pipeline 弥补了原始 MLlib 库的不足,向用户提供了一个基于 DataFrame 的机器学习工作流式 API 套件。

使用 ML Pipeline API可以很方便的把数据处理,特征转换,正则化,以及多个机器学习算法联合起来,构建一个单一完整的机器学习流水线。这种方式给我们提供了更灵活的方法,更符合机器学习过程的特点,也更容易从其他语言迁移。Spark官方推荐使用spark.ml。如果新的算法能够适用于机器学习管道的概念,就应该将其放到spark.ml包中,如:特征提取器和转换器。开发者需要注意的是,从Spark2.0开始,基于RDD的API进入维护模式(即不增加任何新的特性),并预期于3.0版本的时候被移除出MLLib。

Spark在机器学习方面的发展非常快,目前已经支持了主流的统计和机器学习算法。纵观所有基于分布式架构的开源机器学习库,MLlib可以算是计算效率最高的。MLlib目前支持4种常见的机器学习问题: 分类、回归、聚类和协同过滤。下表列出了目前MLlib支持的主要的机器学习算法:

spark周边_第10张图片

MLlib 是spark的可以扩展的机器学习库,由以下部分组成:通用的学习算法和工具类,包括分类,回归,聚类,协同过滤,降维,当然也包括调优的部分

  • Data types
  • Basic statistics (基本统计)
  • summary statistics 概括统计
  • correlations 相关性
  • stratified sampling 分层取样
  • hypothesis testing 假设检验
  • random data generation 随机数生成
  • Classification and regression (分类一般针对离散型数据而言的,回归是针对连续型数据的。本质上是一样的)
  • linear models (SVMs, logistic regression, linear regression)线性模型(支持向量机,逻辑回归,线性回归)
  • naive Bayes 贝叶斯算法
  • decision trees 决策树
  • ensembles of trees (Random Forests and Gradient-Boosted Trees)
    多种树(随机森林和梯度增强树)
  • Collaborative filtering 协同过滤
  • alternating least squares (ALS) (交替最小二乘法(ALS) )
  • Clustering 聚类
  • k-means k均值算法
  • Dimensionality reduction (降维)
  • singular value decomposition (SVD) 奇异值分解
  • principal component analysis (PCA) 主成分分析
  • Feature extraction and transformation 特征提取和转化
  • Optimization (developer) 优化部分
  • stochastic gradient descent 随机梯度下降
  • limited-memory BFGS (L-BFGS) 短时记忆的BFGS (拟牛顿法中的一种,解决非线性问题)

4. 线性回归实例

import org.apache.spark.SparkConf;  
import org.apache.spark.api.java.JavaDoubleRDD;  
import org.apache.spark.api.java.JavaRDD;  
import org.apache.spark.api.java.JavaSparkContext;  
import org.apache.spark.api.java.function.Function;  
import org.apache.spark.mllib.evaluation.RegressionMetrics;  
import org.apache.spark.mllib.linalg.Vectors;  
import org.apache.spark.mllib.regression.LabeledPoint;  
import org.apache.spark.mllib.regression.LinearRegressionModel;  
import org.apache.spark.mllib.regression.LinearRegressionWithSGD;  
  
import scala.Tuple2;  
  
public class SparkMLlibLinearRegression {  
  
    public static void main(String[] args) {  
          
        String path = "file:///data/hadoop/spark-2.0.0-bin-hadoop2.7/data/mllib/ridge-data/lpsa.data";  
        SparkConf conf = new SparkConf();  
        JavaSparkContext sc = new JavaSparkContext(args[0], "Spark", conf);     
           
        JavaRDD data = sc.textFile(path);  
        JavaRDD parsedData = data.map(new Function() {  
            @Override  
            public LabeledPoint call(String line) throws Exception {  
                String[] parts = line.split(",");  
                String[] features = parts[1].split(" ");  
                double[] v = new double[features.length];  
                for (int i = 0; i < v.length; i++) {  
                    v[i] =  Double.parseDouble(features[i]);  
                }  
                return new LabeledPoint(Double.parseDouble(parts[0]), Vectors.dense(v));  
            }  
        });  
        parsedData.cache();  
      
        // Building the model  
        int numIterations = 100;  
        double stepSize = 0.00000001;  
        final LinearRegressionModel model =  
          LinearRegressionWithSGD.train(JavaRDD.toRDD(parsedData), numIterations, stepSize);  
  
        // Evaluate model on training examples and compute training error  
        JavaRDD> valuesAndPreds = parsedData.map(new Function>(){  
        @Override  
        public Tuple2 call(LabeledPoint point)  
                    throws Exception {  
            double prediction = model.predict(point.features());  
            return new Tuple2(prediction, point.label());  
        }  
              
        });  
           
        double MSE = new JavaDoubleRDD(valuesAndPreds.map(  
          new Function, Object>() {  
            public Object call(Tuple2 pair) {  
              return Math.pow(pair._1() - pair._2(), 2.0);  
            }  
          }  
        ).rdd()).mean();  
          
        System.out.println("training Mean Squared Error = " + MSE);  
        
        //模型评测  
        JavaRDD>  valuesAndPreds2= parsedData.map(new Function>(){  
            @Override  
            public Tuple2 call(LabeledPoint point)  
                        throws Exception {  
                double prediction = model.predict(point.features());  
                return new Tuple2(prediction, point.label());  
            }  
                  
            });  
         RegressionMetrics metrics = new RegressionMetrics(JavaRDD.toRDD(valuesAndPreds2));  
       System.out.println("R2(决定系数)= "+metrics.r2());   
         System.out.println("MSE(均方差、方差) = "+metrics.meanSquaredError());  
         System.out.println("RMSE(均方根、标准差) "+metrics.rootMeanSquaredError());  
         System.out.println("MAE(平均绝对差值)= "+metrics.meanAbsoluteError());  
           
           
           
        // Save and load model  
        model.save(sc.sc(), "target/tmp/javaLinearRegressionWithSGDModel");  
        LinearRegressionModel sameModel = LinearRegressionModel.load(sc.sc(),  
          "target/tmp/javaLinearRegressionWithSGDModel");  
          
         double result = sameModel.predict(Vectors.dense(-0.132431544081234,2.68769877553723,1.09253092365124,1.53428167116843,-0.522940888712441,-0.442797990776478,0.342627053981254,-0.687186906466865));  
         System.out.println(sameModel.weights());  
         System.out.println("save predict result="+ result);  
           
         result = model.predict(Vectors.dense(-0.132431544081234,2.68769877553723,1.09253092365124,1.53428167116843,-0.522940888712441,-0.442797990776478,0.342627053981254,-0.687186906466865));  
         System.out.println(model.weights());  
         System.out.println("predict result="+ result);  
    }  
  
}  

你可能感兴趣的:(大数据,hadoop)