Spark(三)【Spark核心RDD,SparkOnYarn】

在上一篇文章中,讲了Spark的简单应用开发,在构建数据源的时候,返回了一个RDD对象,所有对数据的操作,都是在这个对象中进行操作,RDD对象是Spark中至为核心的组件,这篇文章就一起来谈谈Spark RDD (resilient distributed dataset)

什么是RDD?

RDD( resilient distributed dataset ) 弹性分布式数据集;RDD代表是一个不可变的、可分区的、支持并行计算的元素集合(类似于Scala中的不可变集合),RDD可以通过HDFS、Scala集合、RDD转换、外部的数据集(支持InputFormat)获得;并且我们可以通知Spark将RDD持久化在内存中,可以非常高效的重复利用或者在某些计算节点故障时自动数据恢复;

如何创建RDD

Scala 集合创建

数组中数字进行求和计算,两种方法

  • parallelize
  • makeRDD (查看源码最终调用的还是 parallelize
package com.netzhuo

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object RDDByCollectionTest extends App {
	// 设置运行核心为本机CPU数量
  val conf: SparkConf = new SparkConf().setAppName("collection").setMaster("local[*]")
  val sc = new SparkContext(conf)
  val range: Range = 1 to 100

//  m1(sc)
//  m2(sc)

  sc.stop()

  /**
    * 集合启动任务二
    * @param sc
    */
  def m2(sc: SparkContext): Unit ={
    val rdd = sc.makeRDD(range,3)

    val sum = rdd.reduce((t1:Int,t2:Int) => t1 + t2)

    println(s"rdd.分区数量 = ${rdd.getNumPartitions}")

    println(sum)
  }


  /**
    * 集合启动任务一
    * @param sc
    */
  def m1(sc: SparkContext): Unit ={
    val rdd: RDD[Int] = sc.parallelize(range,2)
	
    val sum = rdd.reduce((t1:Int,t2:Int) => t1 + t2)

    println(s"rdd.分区数量 = ${rdd.getNumPartitions}")

    println(sum)
  }

}

通过外部数据集构建
文件系统
Local(本地文件系统)

通过本地文件系统创建RDD 两种方法

  • wholeTextFiles
  • textFile
package com.netzhuo

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object RDDByLocalFileTest extends App {

  val conf: SparkConf = new SparkConf().setAppName("localFile").setMaster("local[*]")
  val sc = new SparkContext(conf)

//  m1(sc)
  m2(sc)

  sc.stop()

  /**
    * 方法二
    * 通过本地文件目录创建RDD
    *   输出目录下的所有文件中的内容
    * @param sc
    */
  def m2(sc: SparkContext): Unit ={

    // k:v   k= path v= value
    val rdd:RDD[(String, String)] = sc.wholeTextFiles("file:///G:\\testData",1)

//    rdd.foreach(println)

      rdd
      .map(t2 => t2._2)  // 获取出来每一行数据
      .flatMap(_.split("\\n")) // 对数据按行进行切割
      .flatMap(_.split("\\s"))
      .map((_,1L))
      .groupByKey()
      .map(t2=>(t2._1,t2._2.size))
      .sortBy(t2=>t2._1)  // 分区数量为1的时候进行排序
      .foreach(println(_))
  }


  /**
    * 方法一
    * 通过本地文件目录创建RDD
    *   输出目录下的所有文件中的内容
    * @param sc
    */
  def m1(sc: SparkContext): Unit ={

    val rdd: RDD[String] = sc.textFile("file:///G:\\testData",5)

    rdd.foreach(println)
  }

}

HDFS

使用HDFS文件系统创建RDD两种方法和通过本地文件系统创建方法一样,将文件路径修改为HDFS中的文件

package com.netzhuo

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object RDDByHDFSFileTest extends App {

  val conf: SparkConf = new SparkConf().setAppName("localFile").setMaster("local[*]")
  val sc = new SparkContext(conf)

  m1(sc)
//  m2(sc)

  sc.stop()

  /**
    * 方法二
    * 通过HDFS目录创建RDD
    *   输出目录下的所有文件中的内容
    * @param sc
    */
  def m2(sc: SparkContext): Unit ={

    // k:v   k= path v= value
    val rdd:RDD[(String, String)] = sc.wholeTextFiles("hdfs://SparkOnStandalone:9000/data.txt",1)

//    rdd.foreach(println)

      rdd
      .map(t2 => t2._2)  // 获取出来每一行数据
      .flatMap(_.split("\\n")) // 对数据按行进行切割
      .flatMap(_.split("\\s"))
      .map((_,1L))
      .groupByKey()
      .map(t2=>(t2._1,t2._2.size))
      .sortBy(t2=>t2._1)  // 分区数量为1的时候进行排序
      .foreach(println(_))
  }


  /**
    * 方法一
    * 通过HDFS目录创建RDD
    *   输出目录下的所有文件中的内容
    * @param sc
    */
  def m1(sc: SparkContext): Unit ={

    val rdd: RDD[String] = sc.textFile("hdfs://SparkOnStandalone:9000/data.txt",5)

    rdd.foreach(println)
  }

}

关系型数据库

通过MySQL构建RDD

导入驱动jar包
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

使用 JdbcRDD创建需要指定上下边界(范围查询),不方便
使用 newAPIHadoopRDD 创建需要使用hadoopConfiguration 对象进行设置连接属性

package com.netzhuo

import java.sql.DriverManager

import org.apache.hadoop.io.LongWritable
import org.apache.hadoop.mapreduce.lib.db.{DBConfiguration, DBInputFormat}
import org.apache.spark.rdd.{JdbcRDD, RDD}
import org.apache.spark.{SparkConf, SparkContext}

/**
  * 外部数据源mysql创建RDD
  */
object RDDByMysqlTest extends App {

  val conf: SparkConf = new SparkConf().setAppName("localFile").setMaster("local[*]")
  val sc = new SparkContext(conf)

//  m1(sc)
  m2(sc)

  sc.stop()

  /**
    * 方法二
    * 
    * @param sc
    */
  def m2(sc: SparkContext): Unit = {

    val conf = sc.hadoopConfiguration
    conf.set(DBConfiguration.DRIVER_CLASS_PROPERTY, "com.mysql.jdbc.Driver")
    conf.set(DBConfiguration.URL_PROPERTY, "jdbc:mysql://172.16.5.199:3306/test")
    conf.set(DBConfiguration.USERNAME_PROPERTY, "root")
    conf.set(DBConfiguration.PASSWORD_PROPERTY, "root")
    conf.set(DBConfiguration.INPUT_QUERY, "select * from t_user")
    // 查询数据库表的记录数,用以计算分区
    conf.set(DBConfiguration.INPUT_COUNT_QUERY, "select count(*) from t_user")
    conf.set(DBConfiguration.INPUT_CLASS_PROPERTY, "com.netzhuo.User")


    val rdd = sc.newAPIHadoopRDD(conf, classOf[DBInputFormat[User]], classOf[LongWritable], classOf[User])

    println(s"rdd.getNumPartitions = ${rdd.getNumPartitions}")
    
    rdd
      .foreach(t2 => {
        println("k=" + t2._1 + "    | v=" + t2._2)
      })
  }


  /**
    * 方法一
    *  使用Mysql 库中的数据创建RDD
    * 通过JDBCRDD构建  有比较大的局限性,原因是SQL语句中必须定义上下边界,否则无法使用
    * @param sc
    */
  def m1(sc: SparkContext): Unit ={

    val rdd = new JdbcRDD(
      sc,
      () => {
        Class.forName("com.mysql.jdbc.Driver")
        val connection = DriverManager.getConnection("jdbc:mysql://172.16.5.199:3306/test", "root", "root")
        connection
      },
      "select * from t_user where id >= ? and id <= ?",
      1, // 下边界
      4, // 上边界
      1, // 分区数量
      rs => {
        val id = rs.getInt("id")
        val name = rs.getString("name")
        val sex = rs.getBoolean("sex")
        (id, name, sex)
      }
    )

    rdd.foreach(t3 => println(t3._1,t3._2,t3._3))
  }

}

User 实体类,需要实现DB接口

package com.netzhuo

import java.sql.{PreparedStatement, ResultSet}

import org.apache.hadoop.mapreduce.lib.db.DBWritable

class User extends DBWritable{
  var id:Int = _
  var name: String = _
  var sex: Boolean = _
  override def toString = s"User($id, $name, $sex)"

  override def write(preparedStatement: PreparedStatement): Unit = {
    preparedStatement.setInt(1,id)
    preparedStatement.setString(2,name)
    preparedStatement.setBoolean(3, sex)
  }

  override def readFields(resultSet: ResultSet): Unit = {
    id = resultSet.getInt(1)
    name = resultSet.getString(2)
    sex = resultSet.getBoolean(3)
  }
}

运行
  • 本地执行
  • 打包jar包,远程提交

spark应用在计算时使用到第三方依赖jar包,在spark集群运行时如果没有提供此jar包,则会发生java.lang.ClassNotFoundException

第一种方法:

将第三方依赖jar包,提前准备到spark集群的每一个计算节点

[root@SparkOnStandalone spark-2.4.4]# mkdir 3rdLib
[root@SparkOnStandalone spark-2.4.4]# mv ~/mysql-connector-java-5.1.47.jar /usr/spark-2.4.4/3rdLib/

# 修改spark的配置文件 从而能够加载第三方的驱动jar包
[root@SparkOnStandalone spark-2.4.4]# vi conf/spark-defaults.conf
# 配置文件声明jar包扩展目录
spark.executor.extraClassPath=/usr/spark-2.4.4/3rdLib/*
spark.driver.extraClassPath=/usr/spark-2.4.4/3rdLib/*

# 重启spark集群
[root@SparkOnStandalone spark-2.4.4]# sbin/stop-all.sh
SparkOnStandalone: stopping org.apache.spark.deploy.worker.Worker
SparkOnStandalone: stopping org.apache.spark.deploy.worker.Worker
stopping org.apache.spark.deploy.master.Master
[root@SparkOnStandalone spark-2.4.4]# sbin/start-all.sh

第二种方案【推荐】:

将第三方远程依赖,打包到计算应用中;

# 1. 在maven配置文件中添加一个打包插件
<build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <!--这里要替换成jar包main方法所在类 -->
                            <mainClass>com.netzhuo.RDDByHBaseTest</mainClass>
                        </manifest>
                        <manifestEntries>
                            <Class-Path>.</Class-Path>
                        </manifestEntries>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id> <!-- this is used for inheritance merges -->
                        <phase>package</phase> <!-- 指定在打包节点执行jar包合并操作 -->
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

打包结束后会获得两个jar包:xxx.jarxxx-jar-with-dependencies.jar

  • 第一个jar包是不包含第三方依赖的普通jar包
  • 第二个jar包是包含第三方完整依赖的特殊jar包

包含完整依赖的特殊jar包上传到linux中提交运行

bin/spark-submit --master spark://SparkOnStandalone:7077 --total-executor-cores 2 --class com.netzhuo.RDDByHBaseTest /tmp/SparkDay02-1.0-SNAPSHOT-jar-with-dependencies.jar

第三种方法:

提交spark应用是,添加参数,在线下载需要的第三方依赖;

注意:不适用于MySQL

# 查看帮助
./bin/spark-shell --help

--packages  声明需要依赖的第三方依赖坐标 多个依赖用","分割  如: mysql:mysql-connector-java:5.1.47,....
--repositories 第三方maven镜像仓库地址

[root@SparkOnStandalone spark-2.4.4]#  bin/spark-submit --master spark://SparkOnStandalone:7077 --class com.baizhi.datasource.RDDCreatedByDB --total-executor-cores 2 /root/spark-day2-1.0-SNAPSHOT.jar --packages mysql:mysql-connector-java:5.1.47
通过HBase创建
导入HBase第三方依赖
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-server</artifactId>
    <version>1.4.10</version>
</dependency>

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>1.4.10</version>
</dependency>

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>2.5.0</version>
</dependency>
启动HBase服务,准备外部的数据集

启动HDFS,zookeeper,Hbase
jps 确认环境正常

3506 HRegionServer
3669 Jps
2742 QuorumPeerMain
2183 DataNode
2408 SecondaryNameNode
3353 HMaster
2059 NameNode

scan 'netzhuo1':t_user 查看 t_user 中的数据

开发应用

本地运行或者打包完整jar包远程运行

package com.netzhuo

import java.sql.DriverManager

import org.apache.hadoop.hbase.HConstants
import org.apache.hadoop.hbase.client.Result
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce.TableInputFormat
import org.apache.hadoop.hbase.util.Bytes
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * HBase 创建RDD
  */
object RDDByHBaseTest extends App {

  val conf: SparkConf = new SparkConf().setAppName("localFile").setMaster("local[*]")
  val sc = new SparkContext(conf)

  val configuration = sc.hadoopConfiguration
  configuration.set(HConstants.ZOOKEEPER_QUORUM,"HadoopNode00")
  configuration.set(HConstants.ZOOKEEPER_CLIENT_PORT,"2181")

  // HBase 字段名称
  configuration.set(TableInputFormat.INPUT_TABLE,"netzhuo1:t_user")

  // 查询字段列表
  configuration.set(TableInputFormat.SCAN_COLUMNS,"cf1:name cf1:age")

  val rdd: RDD[(ImmutableBytesWritable, Result)] = sc.newAPIHadoopRDD(
    configuration,
    classOf[TableInputFormat],
    classOf[ImmutableBytesWritable],
    classOf[Result])

  println(s"rdd.getNumPartitions = ${rdd.getNumPartitions}")
  
  rdd.foreach(t2 =>{
    val rowkey = Bytes.toString(t2._1.get())
    val name = Bytes.toString(t2._2.getValue(Bytes.toBytes("cf1"),Bytes.toBytes("name")))
    val age = Bytes.toString(t2._2.getValue(Bytes.toBytes("cf1"),Bytes.toBytes("age")))

    println(rowkey,name,age)
  })


  sc.stop()

}

SparkOnYarn

Spark 的特性 Runs Everywhere,可以在Yarn环境下运行,在这里尝试部署Spark到Yarn环境下

配置系统环境

  • Centos 7

    • 关闭防火墙
    • 配置网络
    • 配置IP地址映射
    • 配置SSH免密登录
  • Java 1.8

  • 安装JDK 1.8以上

  • 安装Hadoop

  • 安装Spark

yarn 环境下提交命令

./bin/spark-submit --master yarn --executor-cores 2 --num-executors 2 --class com.netzhuo.RDDByHBaseTest /tmp/SparkDay02-1.0-SNAPSHOT-jar-with-dependencies.jar

yarn 环境下shell 命令
bin/spark-shell --master yarn --executor-cores 2 --num-executors 2

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值