Spark_07

回顾

线程安全问题的起因:静态类或公用的对象中成员变量进行更改。

about:DataFrame
DataFrame也是一个分布式数据集,是一个描述,不过没有真正的数据。类似于RDD。再执行前会对程序进行优化。
创建方法1:先生成RDD,通过RDD的toDF()将RDD变成DataFrame,
2:通过SparkSession对象获取//val lines: Dataset[String] = spark.read.textFile(“hdfs://node1:8020/words”)

Spark SQL的join

DataFrame数据如何进行join:两种方法:1.创建视图,通过sql进行join;2.不创建视图,使用DataFrame的join方法。

sql案例左外连接
package day7

import org.apache.spark.sql.{DataFrame, Dataset, SparkSession}

object JoinTest {

  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder()
      .appName("JoinTest")
      .master("local[2]")
      .getOrCreate()

    import spark.implicits._
    val lines: Dataset[String] = spark.createDataset(List("1,laozhao,china","2,laozhang,usa","3,laoyang,jp"))
    //对数据进行整理
    val tpDs: Dataset[(Long, String, String)] = lines.map(line =>{
      val fields = line.split(",")
      val id = fields(0).toLong
      val name = fields(1)
      val nationCode = fields(2)
      (id, name, nationCode)
    })

    val df1 = tpDs.toDF("id", "name", "nation")

    val nations: Dataset[String] = spark.createDataset(List("china,中国","usa,美国"))
    //对数据进行整理
    val ndataset:Dataset[(String, String)] = nations.map(l => {
      val fields = l.split(",")
      val ename = fields(0)
      val cname = fields(1)
      (ename, cname)
    })

    val df2 = ndataset.toDF("ename","cname")

    //第一种,sql创建视图
    /*df1.createTempView("v_users")
    df2.createTempView("v_nations")
    val r: DataFrame = spark.sql("SELECT name, cname FROM v_users JOIN v_nations ON nation = ename")
    r.show()*/

    //第二种,再不创建视图的情况下,使用DataFrame的join方法
    val r = df1.join(df2, $"nation" === $"ename", "left") //左连接
    
    r.show()
    
    spark.stop()
  }
}
sql案例IP规则匹配
package day7
import java.sql.{Connection, DriverManager}
import org.apache.spark.sql.{DataFrame, Dataset, SparkSession}
import scala.io.{BufferedSource, Source}

//ip规则匹配,使用SparkSQL
object IPLoactionSQL {

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

    val spark = SparkSession.builder()
      .appName("JoinTest")
      .master("local[2]")
      .getOrCreate()

    //取到HDFS中的IP规则
    import spark.implicits._
    val rulesLines: Dataset[String] = spark.read.textFile(args(0))

    //整理IP规则数据
   val rulDataFrame: DataFrame = rulesLines.map(line => {
      val fields = line.split("[|]")
      val startNum = fields(2).toLong
      val endNum = fields(3).toLong
      val province = fields(6)
      (startNum, endNum, province)
    }).toDF("snum", "enum", "province")

    //创建RDD,读取访问日志
    val accessLines: Dataset[String] = spark.read.textFile(args(1))

    //数据整理
    val ipDataFrame: DataFrame = accessLines.map(log => {
      val fields = log.split("[|]")
      val ip = fields(1)
      //将IP转换成十进制
      val ip_num = MyUtils.ip2Long(ip)
      (ip_num)
    }).toDF("ip_num")

    rulDataFrame.createTempView("v_rules")
    ipDataFrame.createTempView("v_ips")

	//写sql语句的前提是:结构化数据和映射schema信息
    var r = spark.sql("SELECT province, count(*) counts FROM v_ips JOIN v_rules ON (ip_num >= snum AND ip_num <= enum) GROUP BY province")

    r.show()
    spark.stop()
  }
}
object MyUtils{
  //将IP地址转换成十进制
  def ip2Long(ip: String): Long = {
    val fragments = ip.split("[.]")
    var ipNum = 0L
    for(i <- 0 until fragments.length){
      ipNum = fragments(i).toLong | ipNum << 8L
    }
    ipNum
  }
}
优化join

join会产生大量的shuffle,速度非常慢,使用广播变量实现mapsidejoin。

package day7

import java.sql.{Connection, DriverManager}
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.sql.{DataFrame, Dataset, SparkSession}

/**
  * join的代价太昂贵,把规则广播出去
  */
object IPLoactionSQL2 {

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

    val spark = SparkSession.builder()
      .appName("JoinTest")
      .master("local[2]")
      .getOrCreate()


    //取到HDFS中的IP规则
    import spark.implicits._
    val rulesLines: Dataset[String] = spark.read.textFile(args(0))

    //整理IP规则数据
    val rluesDataset = rulesLines.map(line => {
      val fields = line.split("[|]")
      val startNum = fields(2).toLong
      val endNum = fields(3).toLong
      val province = fields(6)
      (startNum, endNum, province)
    })

    //将分散在多个Executor中的部分数据IP规则收集到Driver端
    val rulesInDriver: Array[(Long, Long, String)] = rluesDataset.collect()

    //将Driver端的数据广播到Executor中
    //调用sc上的关播方法
    //广播变量的引用(还在Driver端)
    val broadcastRef: Broadcast[Array[(Long, Long, String)]] = spark.sparkContext.broadcast(rulesInDriver)

    //创建RDD,读取访问日志
    val accessLines: Dataset[String] = spark.read.textFile(args(1))

    //数据整理
    val ipDataFrame: DataFrame = accessLines.map(log =>{
      val fields = log.split("[|]")
      val ip = fields(1)
      //将IP转换成十进制
      val ipNum = MyUtils.ip2Long(ip)
      ipNum
    }).toDF("ip_num")

    //定义一个自定义函数(UDF),并注册;
    //作用:输入一个IP地址返回一个省份的名字。
    spark.udf.register("ip2Province" , (ipNum: Long) => {
      //查找IP规则(事先已经广播了,已经存在Executor中了)
      //函数的逻辑代码是在Executor中执行的,怎样获取IP规则的引用(对应的数据)呢?
      //使用广播引用就可以获得
      val ipRulesInExecutor:Array[(Long, Long ,String)] = broadcastRef.value
      //根据IP地址对应的十进制查找省份的名字
      val index = MyUtils.binarySearch(ipRulesInExecutor, ipNum)
      var province = "未知"
      if(index != -1){
        province = ipRulesInExecutor(index)._3
      }
      province
    })

    ipDataFrame.createTempView("v_log")
    //执行SQL
    val r = spark.sql("SELECT ip2Province(ip_num) province, COUNT(*) counts FROM v_log GROUP BY province ORDER BY counts DESC")

    r.show()

    spark.stop()
  }
}

object MyUtils{

  //将IP地址转换成十进制
  def ip2Long(ip: String): Long = {
    val fragments = ip.split("[.]")
    var ipNum = 0L
    for(i <- 0 until fragments.length){
      ipNum = fragments(i).toLong | ipNum << 8L
    }
    ipNum
  }

  def data2MySQL(it: Iterator[(String, Int)]): Unit = {
    val conn: Connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bigdata?characterEncoding=UTF-8","root","123")
    val pstm = conn.prepareStatement("insert into access_log values (?,?)")
    //将一个分区中的每一条数据拿出来
    it.foreach(tp => {
      pstm.setString(1, tp._1)
      pstm.setInt(2, tp._2)
      pstm.executeUpdate()
    })
    pstm.close()
    conn.close()
  }

  //二分法查找
  def binarySearch(lines: Array[(Long, Long, String)], ip: Long): Int = {
    var low = 0
    var high = lines.length - 1
    while(low <= high){
      val middle = (low + high) / 2
      if((ip >= lines(middle)._1) && (ip <= lines(middle)._2))
        return middle
      if(ip < lines(middle)._1)
        high = middle -1
      else
        low = middle + 1
    }
    -1
  }
}

Spark Sql的三种join

参考连接:
https://blog.csdn.net/rms1800201760/article/details/90970932
https://www.cnblogs.com/0xcafedaddy/p/7614299.html
https://www.imooc.com/article/28031?block_id=tuijian_wz

UDF(user defined function)
UDF		输入一行,返回一个结果			一对一 								ip2Province() 		->			辽宁省
UDTF 	输入一行,返回多行(hive)		一对多									spark SQL中没有UDTF,spark中用flatMap即可实现该功能
UDAF 	输入多行,返回一行					aggregate(聚合) 				count,sum,avg,max,min这些事sparkSQL自带的聚合函数,但是复杂的业务,要自己定义
几何平均数

概念:123*…*n开n次方

object TestPow{
	def main(args: Array[String]) : Unit = {
		val i = 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10
		val r = Math.pow(i, 1.toDouble/10)
	}
}

自定义聚合函数

几何平均数分布式算法实现

package day7

import java.lang
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types.{DataType, DoubleType, LongType, StructField, StructType}
import org.apache.spark.sql.{Dataset, Row, SparkSession}

object UdafTest {

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

    val spark = SparkSession.builder()
      .appName("JoinTest")
      .master("local[2]")
      .getOrCreate()

    val geomean = new GeoMean

    val range: Dataset[lang.Long] = spark.range(1,11)

    //注册函数
    //spark.udf.register("gm", geomean)
    //将range这个Dataset[Long]注册成视图
    //range.createTempView("v_range")
    //val result = spark.sql("SELECT gm(id) result FROM v_range")

    import spark.implicits._
    val result = range.groupBy().agg(geomean($"id").as("geomean"))

    result.show()

    spark.stop()
  }
}
//自定义函数需要继承UserDefinedAggregateFunction
class GeoMean extends UserDefinedAggregateFunction{
  //输入数据的类型
  override def inputSchema: StructType = StructType(List(
    StructField("value", DoubleType)
  ))
  //产生中间结果的数据类型
  override def bufferSchema: StructType = StructType(List(
    //相乘之后返回的积
    StructField("product", DoubleType)
    //参与运算的数字的个数
    StructField("counts", LongType),
  ))
  //最终返回的结果类型
  override def dataType: DataType = DoubleType

  //确保一致性 一般用true
  override def deterministic: Boolean = true
  //指定初始值
  override def initialize(buffer: MutableAggregationBuffer): Unit = {
    //相乘的初始值
    buffer(0) = 1.0
    //参与运算数字的个数
    buffer(1) = 0L
  }
  //局部聚合
  //每有一条数据参与运算就更新一下中间结果(update相当于在每一个分区中的运算)
  override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
    //每有一个数字参与运算就进行相乘(包含中间结果)
    buffer(0) = buffer.getDouble(0) * input.getDouble(0)
    //参与运算数据的个数也有更新
    buffer(1)  = buffer.getLong(1) + 1L
  }
  //全局聚合
  override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
    //每个分区计算的结果进行相乘
    buffer1(0) = buffer1.getDouble(0) * buffer2.getDouble(0)
    //每个分区参与预算的中间结果进行相加
    buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
  }

  //计算最终的结果
  override def evaluate(buffer: Row): Any = {
    math.pow(buffer.getDouble(0), 1.toDouble / buffer.getLong(0))
  }
}

Dataset

Dataset是spark1.6以后推出的新的API,也是一个分布式数据集,于RDD相比,保存了很多描述信息,概念上等同于关系型数据库中的二维表,基于保存了很多的描述信息,spark在运行时也可以被优化。
Dataset里面对应的数据是强类型的,并且可以使用功能更加丰富的lambda表达式,弥补了函数编程的一些缺点,使用起来更方便,
在Scala中,DataFrame其实就是Dataset[Row]

Dataset的特点
	1.一系列分区
	2.每个切片上会有对应的函数
	3.依赖关系
	4.kv类型shuffle也会有分区器
	5.如果读取hdfs中的数据会感知最有位置
	6.会优化执行计划
	7.支持更加智能的数据源

调用Dataset的方法先会生成逻辑计划,然后被spark的优化器进行优化,最终生成物理计划,然后提交集群中运行!

Spark SQL的数据源

JDBC数据源

package day7
import java.util.Properties
import org.apache.spark.sql.{DataFrame, SparkSession}
object JdbcDataSource {
  def main(args: Array[String]): Unit = {

    val spark = SparkSession.builder().appName("JdbcDataSource")
      .master("local[*]")
      .getOrCreate()

    import spark.implicits._
    //load这个方法会读取真正的从数据库中读取数据吗?
    //load方法会连接数据库,并获取表头信息,但不会读取真正的数据。
    val logs: DataFrame = spark.read.format("jdbc").options({
      Map("url" -> "jdbc:mysql://localhost:3306/spark_about",
          "driver" -> "com.mysql.jdbc.Driver",
          "dbtable" -> "logs",
          "user" -> "root",
          "password" -> "123")
    }).load()

    //logs.printSchema()
    //logs.show()

    /*val filtered:DataFrame = logs.filter(r => {
      r.getAs[Int](2) >= 20
    })*/

    //val r = logs.filter($"age" >= 20)

    val result = logs.select($"id", $"name", $"age" * 10 as "age")

    val props = new Properties()
    props.put("user", "root")
    props.put("password", "123")
    //将数据保存到数据库
    //ignore参数:如果表已经存在不会追加也不会覆盖。
    //overwrite(覆盖),ignore(),append(追加),error
    //result.write.mode("ignore").jdbc("jdbc:mysql://localhost:3306/spark_about", "logs1", props)

    //DataFrame保存成text时出错,保存到文本中,只有一列的才可以保存,多列不能保存,而且改行只能是文本。
    //result.write.text("C:\\Users\\Desktop\\text")

    result.write.json("C:\\Users\\Desktop\\json")//有表头信息

    result.write.csv("C:\\Users\Desktop\\csv")//无表头信息

    result.write.parquet("C:\\Users\\Desktop\\par")//

    //result.show()
    spark.stop()
  }
}

json数据源

package day7

import org.apache.spark.sql.{DataFrame, SparkSession}
object JsonDataSource {
  def main(args: Array[String]): Unit = {

    val spark = SparkSession.builder().appName("JsonDataSource")
      .master("local[*]")
      .getOrCreate()

    import spark.implicits._

    //指定以后从哪里读取json数据(有表头)
    val jsons: DataFrame = spark.read.json("C:\\Users\\Desktop\\json")

    //当json文件发生改变时,再次运行程序会发生效验和异常,删除效验文件可解决该问题。
	//json文件发生改变,生成的效验和与效验和文件中的数值不一致,可以删除效验和文件,能够解决该问题。
    val result = jsons.where($"age" >= 20)

    result.show()
    spark.stop()
  }
}

CSV数据源

package day7

import org.apache.spark.sql.{DataFrame, SparkSession}

object CsvDataSource {

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

    val spark = SparkSession.builder().appName("CsvDataSource")
      .master("local[*]")
      .getOrCreate()

    import spark.implicits._

    //指定以后从哪里读取数据
    val csv: DataFrame = spark.read.csv("C:\\Users\\Desktop\\csv")

    csv.printSchema()
    //定义别名
    val result = csv.toDF("id", "name", "age")

    result.show()
    spark.stop()
  }
}

Parquet数据源

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

    val spark = SparkSession.builder().appName("ParquetDataSource")
      .master("local[*]")
      .getOrCreate()

    //指定以后从哪里读取数据
    val parquetLine: DataFrame = spark.read.format("parquet").load("C:\\Users\\刘元帅\\Desktop\\par")

    parquetLine.printSchema()
	//show方法是Action
    parquetLine.show()

    spark.stop()
  }
}

有点:parquet文件,及保存了数据,又保存了schema信息,相同列的数据保存到了一起。
进行查询的时候,需要那一列数据就可以将那一列数据取出来,减轻了IO的压力。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值