大数据入门之分布式计算框架Spark(2) -- Spark SQL

1.Spark SQL概述

一个运行在Spark上执行sql的处理框架,可以用来处理结构化的数据【外部数据源(访问hive、json、parquet等文件的数据)】。

Spark SQL提供了SQL的API、DateFrame和DataSet的API

2.Spark SQL架构

大数据入门之分布式计算框架Spark(2) -- Spark SQL_第1张图片

    前端可以有不同种的访问方式,Hive AST传过来的就是一个字符串(解析成抽象语法树),在Catalyst会生成一个未解析完成的执行计划,这个执行计划会和元数据中的表信息进行整合生成逻辑执行计划,经过优化,生成优化执行计划,最后才会生成一个物理执行计划(Spark执行作业),提交到对应的集群上运行。

3.Spark SQL使用

3.1 SparkSession简单使用

3.1.1 pom.xml依赖

        
             2.11.8
             2.1.0
        


        
        
            org.scala-lang
            scala-library
            ${scala.version}
            provided
        

        
        
        
            org.apache.spark
            spark-sql_2.11
            ${spark.version}
            provided
        

3.1.2 编写测试类  (这里用scala语言编写)

object SparkSessionApp {

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

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

    val people = spark.read.json("file:///D:/people.json")
    people.show()

    spark.stop()
  }
}

    运行结果如下:

大数据入门之分布式计算框架Spark(2) -- Spark SQL_第2张图片

3.2 Spark访问Hive数据库

    因为Hive的元数据信息是存放在MySQL中的,所以我们应该在spark的conf/目录下,复制一份hive-site.xml过来




	
		javax.jdo.option.ConnectionURL
		jdbc:mysql://localhost:3306/sparksql?createDatabaseIfNotExist=true
	
	
	        javax.jdo.option.ConnectionDriverName
	        com.mysql.jdbc.Driver
	 
	 
	        javax.jdo.option.ConnectionUserName
	        root
	 
	 
	        javax.jdo.option.ConnectionPassword
	        root
	 

	
		hive.metastore.schema.verification
		false
	

    A)启动 spark-shell --master local[2] --jars mysql.jar包

大数据入门之分布式计算框架Spark(2) -- Spark SQL_第3张图片

    B)我们还可以用spark-sql指令去操作Hive数据库

    启动 spark-sql --master local[2] --jars mysql.jar包

大数据入门之分布式计算框架Spark(2) -- Spark SQL_第4张图片

注意:如果要访问操作Hive中的数据,必须要先启动HDFS

    C)使用JDBC访问Hive中的数据

    依赖:

        
            org.apache.spark
            spark-hive_2.11
            ${spark.version}
            
        

    D)使用JDBC操作hive

    thriftserver启动 【修改默认占用端口】

./start-thriftserver.sh --master local[2] --jars /home/Kiku/mysql-connector-java-5.1.27-bin.jar --hiveconf hive.server2.thrift.port=14000

    添加依赖

        
            org.spark-project.hive
            hive-jdbc
            1.2.1.spark2
        

    编写客户端使用JDBC连接代码

object SparkSQLThriftServerApp {

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

    Class.forName("org.apache.hive.jdbc.HiveDriver")

    val conn = DriverManager.getConnection("jdbc:hive2://192.168.6.130:14000", "Kiku", "")
    val pstmt = conn.prepareStatement("select empno, ename, sal from emp")
    val rs = pstmt.executeQuery()
    while (rs.next()) {
      println("empno: " + rs.getInt("empno") + ", ename: " +
        "" + rs.getString("ename") + ", sal: " + rs.getDouble("sal"))
    }

    rs.close()
    pstmt.close()
    conn.close()

  }
}

4.DataFrame&DataSet&RDD

DataSet:分布式的数据集

RDD:分布式的数据集

DataFrame:以列(列名、列的类型、列值)的形式构成的分布式数据集,按照列赋予不同的名称【更像是一张数据表】

RDD和DataFrame的区别?

【RDD如果是用Java/Scala语言开发,底层是跑在JVM上;用python也会跑在Python的运行环境;执行效率可能会不一样】

【DataFrame底层会转换为逻辑执行计划,执行效率相等】

4.1 DataFrame API使用

/**
  * DataFrame API基本操作
  */
object DataFrameApp {

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

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

    //将json文件加载成一个dataframe
    val peopleDF = spark.read.format("json").load("file:///D:/people.json")

    //输出dataframe对应的schema信息
    peopleDF.printSchema()

    //输出数据集的前20条记录
    peopleDF.show()

    //查询某列所有的数据  select name from table
    peopleDF.select("name").show()

    //查询某几列所有的数据,并对列进行计算:select name, age + 10 as age2 from table
    peopleDF.select(peopleDF.col("name"), (peopleDF.col("age") + 10).as("age2")).show()

    //根据某一列的值进行过滤:select * from table where age > 19
    peopleDF.filter(peopleDF.col("age") > 19).show()

    //根据某一列进行分组,然后再进行聚合操作:select age, count(1) from table group by age
    peopleDF.groupBy("age").count().show()

    spark.stop()

  }
}

4.2 DataFrame和RDD的互操作

1)反射方式:case class

2)编程方式:定义struct,和rdd绑定

/**
  * DataFrame和RDD的互操作
  */
object DataFrameRDDApp {

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

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

    //inferReflection(spark)

    program(spark)


    spark.stop()
  }

  // 利用编程
  private def program(spark: SparkSession): Unit = {
    //RDD => DataFrame
    val rdd = spark.sparkContext.textFile("file:///D:/infos.txt")

    val infoRDD = rdd.map(_.split(",")).map(line => Row(line(0).toInt, line(1), line(2).toInt))

    val structType = StructType(Array(StructField("id", IntegerType, true),
      StructField("name", StringType, true),
      StructField("age", IntegerType, true)))

    val infoDF = spark.createDataFrame(infoRDD, structType)

    infoDF.printSchema()
    infoDF.show()

    //通过df的api进行操作
    infoDF.filter(infoDF.col("age") > 30).show()

    //通过sql的方式进行操作
    //注册为一张表 表名是infos
    infoDF.createOrReplaceTempView("infos")
    spark.sql("select * from infos where age > 30").show()

  }

  // 利用反射
  private def inferReflection(spark: SparkSession) = {
    //RDD => DataFrame
    val rdd = spark.sparkContext.textFile("file:///D:/infos.txt")

    //注意:需要导入隐式转换
    import spark.implicits._
    val infoDF = rdd.map(_.split(",")).map(line => Info(line(0).toInt, line(1), line(2).toInt)).toDF()

    infoDF.show()
    infoDF.filter(infoDF.col("age") > 30).show()

    // 如果对于编程不熟悉 可以利用sql的方式进行处理,注册为一张表 表名是infos
    infoDF.createOrReplaceTempView("infos")
    spark.sql("select * from infos where age > 30").show()
  }

  case class Info(id: Int, name: String, age: Int)

}

4.3 DataFrame API进阶


/**
  * DataFrame中的操作,综合实例
  */
object DataFrameCase {

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

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

    //RDD => DataFrame
    val rdd = spark.sparkContext.textFile("file:///D:/student.txt")

    //注意:需要导入隐式转换
    import spark.implicits._
    val studentDF = rdd.map(_.split("\\|")).map(line => Student(line(0).toInt, line(1), line(2), line(3))).toDF()

    //最多显示30行数据,false:表示长的数据不会被截取掉
    studentDF.show(30, false)

    //取出前10条记录
    studentDF.take(10)
    studentDF.first()
    studentDF.head(3)

    //截取其中的几列
    studentDF.select("email").show(30, false)

    studentDF.filter("name='' OR name='NULL'").show()

    //找出name以M开头的人
    studentDF.filter("SUBSTR(name, 0, 1)='M'").show()

    //按照name进行排序
    studentDF.sort(studentDF.col("name")).show()
    studentDF.sort(studentDF.col("name").desc).show()

    studentDF.sort("name", "id").show()
    studentDF.sort(studentDF.col("name").asc, studentDF.col("id").desc).show()

    studentDF.select(studentDF.col("name").as("student_name")).show()

    //join
    val studentDF2 = rdd.map(_.split("\\|")).map(line => Student(line(0).toInt, line(1), line(2), line(3))).toDF()

    studentDF.join(studentDF2, studentDF.col("id") === studentDF2.col("id")).show()

    spark.stop()

  }

  //模式类:实例化对象的时候可以不用new,默认public,有默认的toString方法,不能被继承
  case class Student(id: Int, name: String, phone: String, email: String)

}

5.Spark SQL外部数据源

5.1 parquet文件

/**
  * Parquet文件操作
  */
object ParquetApp {

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

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

    /**
      * spark.read.format("parquet").load 这是标准写法
      */
    val userDF = spark.read.format("parquet").load("file:///home/Kiku/app/spark-2.1.0-bin-2.6.0-cdh5.7.0/examples/src/main/resources/users.parquet")
    userDF.printSchema()
    userDF.show()

    userDF.select("name", "favorite_color").show()
    //把读出来的两列 转换成json写入新文件中
    userDF.select("name", "favorite_color").write.format("json").save("file:///home/Kiku/data/jsonout")


    spark.read.load("file:///home/Kiku/app/spark-2.1.0-bin-2.6.0-cdh5.7.0/examples/src/main/resources/users.parquet").show()

    //会报错,因为sparksql默认处理的format就是parquet
    spark.read.load("file:///home/Kiku/app/spark-2.1.0-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.json").show()

    spark.read.format("parquet").option("path", "file:///home/Kiku/app/spark-2.1.0-bin-2.6.0-cdh5.7.0/examples/src/main/resources/users.parquet").load().show()

    //调整默认分区大小 默认是200
    spark.sqlContext.setConf("spark.sql.shuffle.partitions", "10")

    //操作mysql 的数据
    spark.read.format("jdbc").option("url", "jdbc:mysql://localhost:3306/sparksql")
        .option("dbtable", "sparksql.TBLS").option("user", "root")
        .option("password", "MySQLKenan_07").option("driver", "com.mysql.jdbc.Driver").load()

    spark.stop()
  }

}

5.2 关联Hive和MySQL进行查询

import org.apache.spark.sql.SparkSession

/**
  * 使用外部数据源综合查询Hive和MySQL的表数据
  */
object HiveMySQLApp {

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

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

    //加载Hive表的数据
    val hiveDF = spark.table("emp")

    //加载mysql的数据
    val mysqlDF = spark.read.format("jdbc")
      .option("url", "jdbc:mysql://localhost:3306/sparksql")
      .option("dbtable", "sparksql.DEPT")
      .option("user", "root").option("password", "MySQLKenan_07")
      .option("driver", "com.mysql.jdbc.Driver").load()

    //JOIN
    val resultDF = hiveDF.join(mysqlDF, hiveDF.col("deptno") === mysqlDF.col("DEPTNO"))

    resultDF.select(hiveDF.col("empno"), hiveDF.col("ename"),
      mysqlDF.col("deptno"), mysqlDF.col("dname")).show()

    resultDF.show()

    spark.stop()
  }
}

5.3 读取Json文件

A) 比如{"age":[1, 2, 3]}  这样我们可以通过select age[0] from table;   去访问数组中的某一个元素

运用explode函数,select explode(age) from table; 我们可以获得age的三行记录

B) 比如{"address":{"province":"山东","city":"青岛"}} 这样我们可以通过select address.city from table;去访问address中的元素

6.性能调优

思路:

列式存储(parquet文件)

分区裁剪

条件下压(where、join,数据分析的时候,我们可以在数据源端进行数据过滤,不加载某些不必要的列,可以边读数据边过滤)

压缩格式选择:

parquet文件默认是snappy压缩格式,可以根据SparkSession.builder().config("spark.sql.parquet.compression.codec","gzip")更改压缩格式

Bzip2压缩效果明显是最好的,但是bzip2压缩速度最慢,可分割

Gzip压缩效果不如Bzip2,但是压缩解压速度快,不支持分隔

LZO压缩效果不如Bzip2和Gzip,但是压缩解压速度最快!并且支持分隔!

插入数据库的优化

对每个分区进行批量插入,在插入的时候,用addBatch,批量插入数据

参数优化

调整并行度

spark-submit \
--class com.xq.analysis.yarn.CategoryCommendAnalysisYARN \
--name CategoryCommendAnalysisYARN \
--master yarn \
--executor-memory 1G \
--num-executors 1 \
--conf spark.sql.shuffle.partitions=100
/home/Kiku/lib/project/sparksqlproject-1.0-SNAPSHOT-jar-with-dependencies.jar \
hdfs://hadoop000:8020/project/clean/category-commend

调整分区字段类型推测【所有分区类型都是string】

SparkSession.builder().config("spark.sql.sources.partitionColumnTypeInterface.enabled", "false");

7.打包提交到YARN上执行

7.1 去掉运行模式、作业名称等参数

val spark = SparkSession.builder().getOrCreate()

7.2 给环境中存在的依赖加入provided,加入plugin

            
                maven-assembly-plugin
                
                    
                        
                            
                        
                    
                    
                        jar-with-dependencies
                    
                
            

7.3 打包

mvn assembly:assembly

7.4 提交执行

spark-submit \
--class com.xq.analysis.yarn.CategoryCommendAnalysisYARN \
--name CategoryCommendAnalysisYARN \
--master yarn \
--executor-memory 1G \
--num-executors 1 \
/home/Kiku/lib/project/sparksqlproject-1.0-SNAPSHOT-jar-with-dependencies.jar \
hdfs://hadoop000:8020/project/clean/category-commend

如果有需要添加的文件,用--files添加

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