[大数据]Spark(2)RDD(3)

3.数据读取与保存

Spark的数据读取及数据保存可以从两个维度来作区分:文件格式以及文件系统。
文件格式分为:Text文件、Json文件、Csv文件、Sequence文件以及Object文件;
文件系统分为:本地文件系统、HDFS以及数据库。

3.1 文件类数据读取与保存

3.1.1 Text文件

1)数据读取:textFile(String)
2)数据保存:saveAsTextFile(String)
3)代码实现:

object Operate_Text {

    def main(args: Array[String]): Unit = {

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[1]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3.1 读取输入文件
        val inputRDD: RDD[String] = sc.textFile("input/1.txt")

        //3.2 保存数据
        inputRDD.saveAsTextFile("output")

        //4.关闭连接
        sc.stop()
    }
}

4)注意:如果是集群路径:hdfs://hadoop102:9000/input/1.txt

3.1.2 Json文件

如果JSON文件中每一行就是一个JSON记录,那么可以通过将JSON文件当做文本文件来读取,然后利用相关的JSON库对每一条数据进行JSON解析。

1)数据准备
在input目录下创建test.json文件,里面存储如下内容。

{"username": "zhangsan","age": 20}
{"username": "lisi","age": 18}
{"username": "wangwu","age": 16}

textFile是一行一行的读入数据,因此需要将json文件清洗成能够识别的格式。

2)代码实现

/**
 * 读取Json格式数据
 */
object Spark03_readJson {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark03_readJson ").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD
    val rdd: RDD[String] = sc.textFile("E:\\spark-0701\\input\\test.json")

    val resRDD: RDD[Option[Any]] = rdd.map(JSON.parseFull)

    resRDD.collect().foreach(println)
    //关闭连接
    sc.stop()
  }

}

输出:

Some(Map(username -> zhangsan, age -> 20.0))
Some(Map(username -> lisi, age -> 18.0))
Some(Map(username -> wangwu, age -> 16.0))

注意:使用RDD读取JSON文件处理很复杂,同时SparkSQL集成了很好的处理JSON文件的方式,所以应用中多是采用SparkSQL处理JSON文件。

3.1.3 Sequence文件

SequenceFile文件是Hadoop用来存储二进制形式的key-value对而设计的一种平面文件(Flat File)。在SparkContext中,可以调用sequenceFile[keyClass, valueClass](path)

代码实现:

object Operate_Sequence {

    def main(args: Array[String]): Unit = {

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[1]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3.1 创建rdd
        val dataRDD: RDD[(Int, Int)] = sc.makeRDD(Array((1,2),(3,4),(5,6)))

        //3.2 保存数据为SequenceFile
        dataRDD.saveAsSequenceFile("output")

        //3.3 读取SequenceFile文件
        sc.sequenceFile[Int,Int]("output").collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}

2)注意:SequenceFile文件只针对PairRDD

3.1.4 Object对象文件

对象文件是将对象序列化后保存的文件,采用Java的序列化机制。可以通过objectFilek,v函数接收一个路径,读取对象文件,返回对应的RDD,也可以通过调用saveAsObjectFile()实现对对象文件的输出。因为是序列化所以要指定类型。

1)代码实现

object Operate_Object {

    def main(args: Array[String]): Unit = {

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[1]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3.1 创建RDD
        val dataRDD: RDD[Int] = sc.makeRDD(Array(1,2,3,4))

        //3.2 保存数据
        dataRDD.saveAsObjectFile("output")

        //3.3 读取数据
        sc.objectFile[(Int)]("output").collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}

3.2 文件系统类数据读取和保存

3.2.1 HDFS

Spark的整个生态系统与Hadoop是完全兼容的,所以对于Hadoop所支持的文件类型或者数据库类型,Spark也同样支持。另外,由于Hadoop的API有新旧两个版本,所以Spark为了能够兼容Hadoop所有的版本,也提供了两套创建操作接口。对于外部存储创建操作而言,hadoopRDD和newHadoopRDD是最为抽象的两个函数接口。

3.2.2 MySQL

支持通过Java JDBC访问关系型数据库。需要通过JdbcRDD进行,示例如下:

1)添加依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.27</version>
</dependency>

2)准备数据,建立test数据库下user表
在这里插入图片描述
3)从MySql中读取数据:


/**
 * 从MySql中读取数据 jdbc
 *
 * RDD参数:
 * sc: SparkContext,    Spark程序执行的入口,上下文对象
 * getConnection: () => Connection, 获取数据库连接
 * sql: String, 执行SQL语句
 * lowerBound: Long, 查询的起始位置
 * upperBound: Long, 查询的结束位置
 * numPartitions: Int, 分区数
 * mapRow: (ResultSet) => T = JdbcRDD.resultSetToObjectArray _ 对结果集的处理
 *
 * 步骤:
 * 1.注册驱动
 * 2.获取连接
 * 3.创建数据库操作对象PrepareStatement
 * 4.执行SQL
 * 5.处理结果集
 * 6.关闭连接
 */
object Spark04_MySQL_read {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark04_MySQL_read ").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //数据库连接4要素
    var driver = "com.mysql.jdbc.Driver"
    var url = "jdbc:mysql://hadoop102:3306/test"
    var username = "root"
    var password = "000000"

    var sql: String = "select * from user where id >= ? and id <= ?"

    //创建RDD
    val resRDD = new JdbcRDD(
      sc,
      () => {
        //注册驱动
        Class.forName(driver)
        //获取连接
        DriverManager.getConnection(url, username, password)
      },
      sql,
      1,
      20,
      2,
      rs => (rs.getInt(1), rs.getString(2), rs.getInt(3))
    )

    //打印输出
    resRDD.collect().foreach(println)

    //关闭连接
    sc.stop()
  }
}

输出:

(1,xuzhu,20)
(2,duanyu,30)
(3,qiaofeng,40)
(4,lisi,20)
(5,zs,30)

4)向MySql中写入数据

object Spark05_MySQL_write {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark05_MySQL_write").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //数据库连接4要素
    var driver = "com.mysql.jdbc.Driver"
    var url = "jdbc:mysql://hadoop102:3306/test"
    var username = "root"
    var password = "000000"


    //创建RDD
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("banzhang", 30), ("jingjing", 18)))
    
    rdd.foreachPartition {
      //datas是RDD一个分区的数据 每个分区创建一个连接
      datas => {
        //1.注册驱动
        Class.forName(driver)
        //2.获取连接
        val conn: Connection = DriverManager.getConnection(url, username, password)
        //3.创建数据库操作对象PrepareStatement
        //声明数据库操作的SQL语句
        val sql: String = "insert into user(name,age) values(?,?)"
        val ps: PreparedStatement = conn.prepareStatement(sql)
        //这里的foreach不是算子,是集合的算子,所以不需要序列化ps
        datas.foreach {
          case (name, age) => {
            ps.setString(1, name)
            ps.setInt(2, age)
            //4.执行SQL
            ps.executeUpdate()
          }
        }
        //6.关闭连接
        ps.close()
        conn.close()
      }
    }
    //关闭连接
    sc.stop()
  }
}

执行结果:
在这里插入图片描述

4.累加器

累加器:分布式共享只写变量。(Task和Task之间不能读数据)
累加器用来对信息进行聚合,通常在向Spark传递函数时,比如使用map()函数或者用 filter()传条件时,可以使用驱动器程序中定义的变量,但是集群中运行的每个任务都会得到这些变量的一份新的副本,更新这些副本的值也不会影响驱动器中的对应变量。如果我们想实现所有分片处理时更新共享变量的功能,那么累加器可以实现我们想要的效果。

代码示例:

object Spark06_Accumulator {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark06_Accumulator").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建rdd
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("a", 4)))

    //遍历求和
    var sum: Int = 0
    rdd.foreach {
      case (word, count) => {
        sum += count
      }
    }

    println(sum)

    //关闭连接
    sc.stop()
  }

}

输出:

0

在这里插入图片描述

在执行sum的时候只是在excutor上执行,即在副本上执行,而最后的输出是在Driver上的sum,副本上的执行并不会做修改,所以输出0。

要实现将excutor上的数据在Driver上聚合,就需要实现累加器。

4.1 系统累加器

通过在驱动器中调用SparkContext.accumulator(initialValue)方法(老版本),创建出存有初始值的累加器。返回值为org.apache.spark.Accumulator[T]对象,其中T是初始值initialValue的类型。Spark闭包里的执行器代码可以使用累加器的+=方法(在Java中是 add)增加累加器的值。驱动器程序可以调用累加器的value属性(在Java中使用value()或setValue())来访问累加器的值。
代码示例:

val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("a", 4)))
val sum: LongAccumulator = sc.longAccumulator("myAcc")
rdd.foreach {
 case (word, count) => {
   sum.add(count) //调用累加器的add方法
 }
}
print(sum.value)
//关闭连接
sc.stop()

输出:

10

注意:
(1)工作节点上的任务不能相互访问累加器的值。从这些任务的角度来看,累加器是一个只写变量。
(2)对于要在行动操作中使用的累加器,Spark只会把每个任务对各累加器的修改应用一次。因此,如果想要一个无论在失败还是重复计算时都绝对可靠的累加器,我们必须把它放在foreach()这样的行动操作中。转化操作中累加器可能会发生不止一次更新。

4.2 自定义累加器

自定义累加器类型的功能在1.X版本中就已经提供了,但是使用起来比较麻烦,在2.0版本后,累加器的易用性有了较大的改进,而且官方还提供了一个新的抽象类:AccumulatorV2来提供更加友好的自定义类型累加器的实现方式。
1)自定义累加器步骤
(1)继承AccumulatorV2,设定输入、输出泛型
(2)重写方法
2)需求:自定义累加器,统计集合中首字母为“H”单词出现的次数。
List(“Hello”, “Hello”, “Hello”, “Hello”, “Hello”, “Spark”, “Spark”)

3)代码实现

/**
 * 自定义累加器,统计出RDD中所有以H开头的单词
 */

object Spark07_Accumulator {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark01_CreateRDD_mem").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD
    val rdd: RDD[String] = sc.makeRDD(List("Hello", "Hello", "Hello", "Hello", "Hello", "Spark", "Spark"))

    //创建累加器对象
    val myAcc = new MyAccumulator()

    //注册累加器
    sc.register(myAcc)

    //使用累加器
    rdd.foreach {
      word => {
        myAcc.add(word)
      }
    }

    //输出累加器结果
    println(myAcc.value)

    //关闭连接
    sc.stop()
  }

}

//定义一个类,集成AccumulatorV2
//泛型累加器输入和输出数据的类型
class MyAccumulator extends AccumulatorV2[String, mutable.Map[String, Int]] {
  //定义一个集合,集合单词以及出现次数
  var map = mutable.Map[String, Int]()

  //是否为初始状态
  override def isZero: Boolean = map.isEmpty

  //拷贝
  override def copy(): AccumulatorV2[String, mutable.Map[String, Int]] = {
    val newAcc = new MyAccumulator
    newAcc.map = this.map
    newAcc
  }

  //重置
  override def reset(): Unit = map.clear()

  //向累加器中添加元素
  override def add(v: String): Unit = {
    if (v.startsWith("H")) {
      map(v) = map.getOrElse(v, 0) + 1 //获取原来的值,并+1
    }
  }

  //合并
  override def merge(other: AccumulatorV2[String, mutable.Map[String, Int]]): Unit = {
    var map1 = map //当前excutor的map
    var map2 = other.value //另一个excutor的map
    map = map1.foldLeft(map2) {
      //mm表示map2,kv表示map1中的每一个元素
      (mm, kv) => {
        val k: String = kv._1
        val v: Int = kv._2
        //根据map1中的元素key,到map2中获取value
        mm(k) = mm.getOrElse(k, 0) + v
        mm
      }
    }

  }

  //获取累加器的值
  override def value: mutable.Map[String, Int] = map
}

输出:

Map(Hello -> 5)

5. 广播变量

广播变量:分布式共享只读变量。
在多个并行操作中(Executor)使用同一个变量,Spark默认会为每个任务(Task)分别发送,这样如果共享比较大的对象,会占用很大工作节点的内存。
广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个或多个Spark操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表,甚至是机器学习算法中的一个很大的特征向量,广播变量用起来都很顺手。
1)使用广播变量步骤:
(1)通过对一个类型T的对象调用SparkContext.broadcast创建出一个Broadcast[T]对象,任何可序列化的类型都可以这么实现。
(2)通过value属性访问该对象的值(在Java中为value()方法)。
(3)变量只会被发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节点)。
2)原理说明
在这里插入图片描述

3)代码示例:

object Spark08_Broadcast {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark08_Broadcast").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD 实现类似Join效果(a,(1,4))(b,(2,5),(c,(3,6))
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)))
    val list = List(("a", 4), ("b", 5), ("c", 6))

    //创建一个广播变量
    val broadcastList: Broadcast[List[(String, Int)]] = sc.broadcast(list)

    //合并
    val resRDD: RDD[(String, (Int, Int))] = rdd.map {
      case (k1, v1) => {
        var v3 = 0
        for ((k2, v2) <- broadcastList.value) {
          if (k1 == k2) {
            v3 = v2
          }
        }
        (k1, (v1, v3))
      }
    }

    resRDD.collect().foreach(println)
    //关闭连接
    sc.stop()
  }

}

输出:

(a,(1,4))
(b,(2,5))
(c,(3,6))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值