sparksql

Spark SQL
一、sparkSQL的特点
1.支持多种数据源:hive RDD Partquet JSON JDBC
2.多种性能优化技术:in-memory columnar storage \ byte-code generation \ cost model 动态评估
3.组件扩展性:对于SQL的语法解析器、分析器、以及优化器,用户都可以自己重新开发,并且动态扩展

Spark sql 的性能优化技术简介

1.内存列存储(in-memory columnar storage)
内存列存储意味着spark sql的数据,不是使用java对象的方式进行存储,而是使用面向列存储的方式进行存储,也就是说,每一列,作为一个数据存储的单位,从而大大优化内存使用的效率。使用列存储之后,减少对内存的消耗,也就避免了对GC(垃圾回收)大量数据的性能开销

2.字节码生成技术(byte-code generation )
Spark sql在其catalyst模块的Expressions中增加一codegen模块,对于sql语句中的计算表达式,比如select num + num from t 这种sql,就可以使用动态字节码生成技术来优化其性能。

3.Scala代码编写的优化
对于Scala代码编写中,可能会造成大量的性能的开销,自己重写,使用更加复杂的方式,来获取更好的性能。比如option样例类、for循环、map/filter/foreach等高阶函数,以及不可变对象,都改成用null,while循环来实现,并且重用可变的对象


二、dataframe的使用
1.spark sql 和 dataframe引言
Spark sql 是spark中的一个模块,主要是进行结构化数据的处理。他提供的最核心的编程抽象,就是dataframe。同时spark sql 还可以作为分布式的sql查询引擎。Spark sql最重要的功能之一就是从hive中查询数据

Dataframe,可以理解为时,以列的形式组织的,分布式的数据集合,他其实和关系型数据库中的表非常类似,但是底层做了很对的优化。Dataframe可以通过很多来源,包括:结构化数据文件,hive表,外部关系型数据库以及RDD


2.SQLContext
要使用spark sql ,首先就得创建一个SQLContext对象,或者是他的子类的对象(HiveContext),比如HiveContext对象;

Java版本:
JavaSparkContext sc = ....;
SQLContext SQLContext = new SQLContext(sc);

Scala版本:
Val sc = SparkContext..
Val SQLContext = new SQLContext(sc)
Import SQLContext.implicits._

3.HiveContext 
除了基本的SQLContext以外,还可以使用它的子类---HiveContext。HiveContext的功能除了包含SQLContext提供的所有的功能外,还包括额外的专门针对hive的一些功能。这些额外的功能包括:使用hive语法编写和执行sql,使用hive的UDF函数,从hive表中读取数据

要使用HiveContext,就必须预先安装好hive,SQLContext支持的数据源,HiveContext也同样支持,而不只是支持hive,对spark1.3.x以上的版本,都推荐使用HiveContext,因为其功能更加丰富和完善

Spark sql 还支持使用spark.sql.dialect参数设置sql方言,使用SQLContext的serConf()即可进行设置。对与SQLContext,他只支持”sql”一种方言。对于HiveContext,它默认的方言是“hiveql”




4.创建Dataframe
使用SQLContext,可以从RDD、hive表中或者其他额数据源,来创建dataframe。

Java版本:
package com.spark.spark_sql;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.SQLContext;

/**
 * 使用json文件创建dataframe
 * @author Administrator
 */
public class DataFrameCreate {

    public static void main(String[] args) {
        SparkConf conf = new SparkConf().setAppName("DataFrameCreate");
        JavaSparkContext sc = new JavaSparkContext(conf);
        SQLContext SQLContext = new SQLContext(sc);
        DataFrame df = SQLContext.read().json("hdfs://hadoop01:8020/spark_input/students.json");
        df.show();

    }
}


提交至集群:运行打包,上传。编写脚本进行提交
【这里是一台机器测试】
bin/spark-submit \
--class com.spark.spark_sql.DataFrameCreate \
--files /opt/modules/apache-hive-0.13.1-bin/conf/hive-site.xml \
--driver-class-path /opt/softwares/mysql-connector-java-5.1.27-bin.jar \
/opt/modules/spark-1.6.1-bin-2.5.0-cdh5.3.6/sql/sparksql_01.jar

 运行的结果
+---+---+--------+
|age| id|    name|
+---+---+--------+
| 10|  1|     leo|
| 25|  2|    kity|
| 30|  4|    lucy|
| 20|  3|     tom|
| 18|  7|    jack|
| 23| 10|  edison|
| 36|  5|    owen|
| 20|  8|    jiny|
| 40|  6|    lisi|
| 45|  9|zhangsan|
+---+---+--------+












Scala版本:
package com.spark.spark_sql
import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.sql.SQLContext

object DataFrameCreate {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("DataFrameCreate")
    val sc = new SparkContext(conf)
    val sqlContext = new SQLContext(sc)
    val df = sqlContext.read.json("hdfs://hadoop01:8020/spark_input/student.json")
    df.show
  }
}


Dataframe常用的操作
java
package com.spark.spark_sql;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.SQLContext;

/**
 * Dataframe的常用的操作
 * @author Administrator
 *
 */
public class DataFrameOperation {

    public static void main(String[] args) {
        SparkConf conf = new SparkConf().setAppName("DataFrameOperation");
        JavaSparkContext sc = new JavaSparkContext(conf);
        SQLContext SQLContext = new SQLContext(sc);
        //创建出来的dataframe完全可以可以理解为一张表
        DataFrame df = SQLContext.read().json("hdfs://hadoop01:8020/input/students.txt");
        //打印dataframe中所有的数据
        df.show();
        //打印dataframe中元数据的信息
        df.printSchema();
        //查询某列所有的数据
        df.select("name").show();
        //查询某几列所有的数据,并对列进行计算
        df.select(df.col("name"), df.col("age").plus(1)).show();//将查询出来的age进行加一
        //根据某一列的值进行过滤
        df.filter(df.col("age").gt(30)).show();                //年龄大于30的进行过滤
        //根据某一列进行分组聚合
        df.groupBy(df.col("age")).count().show();
    }
}

Scala
package com.spark.spark_sql
import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.sql.SQLContext

object DataframeOperation {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("DataframeOperation")
    val sc = new SparkContext(conf)
    val SQLContext = new SQLContext(sc)
    val df = SQLContext.read.json("hdfs://hadoop01:8020/spark_input/student.json" 
    df.show
    df.printSchema()
    df.select("name").show
    df.select(df("name"),df("age")+1).show
    df.filter(df("age") > 30).show
    df.groupBy("age").count().show
 }
}


5.RDD与Dataframe之间转换
为什么要将RDD转换为Dataframe?因为这样的话,我们就可以直接针对hdfs上的任何可以构建为RDD的数据,使用spark sql 进行sql查询,这个功能无比强大。想象一下,针对hdfs中的数据,直接就可以使用sql进行查询

Spark sql 支持两种方式来将RDD转换为Dataframe

第一种方式,是使用反射来推断包含了特定数据类型的RDD的元数据,这种基于反射的方式,代码比较简单,当你已经知道你的RDD的元数据时,是一种非常不错的方式。

第二种方式,是通过编程接口来创建dataframe,你可以在程序运行的时候动态构建一份元数据,然后将其应用到已经存在的RDD上,这种方式的代码比较冗长,但是如果在编写程序时,还不知道RDD的元数据,只有在程序运行时,才能动态得知元数据,那么只能通过这种动态构建元数据的方式。


1.使用反射的方式推断元数据
Java版本:spark sql是支持将包含javaBean的RDD转换为dataframe的,Javabean的信息,就定义了元数据。Spark sql 现在是不支持包含嵌套javabean或者list等复杂元数据的Javabean。

Scala版本:而scala由于具有隐式转换的特性,所以spark sql的scala接口,是支持自动将包含case class 的RDD转换为Dataframe的。Case class 就定义了元数据。Spark sql 会通过反射读取传递给 case class 的参数的名称,然后将其作为列名。与java不同的是,Spark sql是支持将包含了嵌套的数据结构的case class作为元数据的,比如包含了Array等。
Java版本:
package com.spark.spark_sql;
import java.io.Serializable;
public class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    private int id;
    private String name;
    private int age;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Student() {
    }
    public Student(int id, String name, int age) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + ", age=" + age + "]";
    }

}


package com.spark.spark_sql;

import java.util.List;

import org.apache.spark.SparkConf;
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.sql.DataFrame;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SQLContext;

/**
 * 使用反射的方式将RDD转换为dataframe
 * @author Administrator
 *
 */
public class RDD2DataframeReflection {

    public static void main(String[] args) {
        SparkConf conf = new SparkConf().setMaster("local").setAppName("RDD2DataframeReflection");
        JavaSparkContext sc = new JavaSparkContext(conf);
        SQLContext SQLContext = new SQLContext(sc);
        JavaRDD<String> lines = sc.textFile("E://student.txt");
        JavaRDD<Student> students = lines.map(new Function<String, Student>() {
            private static final long serialVersionUID = 1L;

            @Override
            public Student call(String line) throws Exception {
                String[] split = line.split(",");
                Student stu = new Student();
                stu.setId(Integer.parseInt(split[0]));
                stu.setName(split[1]);
                stu.setAge(Integer.parseInt(split[2]));
                return stu;
            }
        });
        //使用反射的方式将RDD转换为dataframe
        
        //将student.class传入进去其实就是通过反射的方式来创建dataframe
        //因为student.class 本身就是反射的一个应用
        //然后底层还得通过student class 进行反射。来获取其中的fields
        //这里要求Javabean要实现Serializable接口,可以序列化
DataFrame studentDF = sqlContext.createDataFrame(students, Student.class);
        
        //拿到一个dataframe之后,就可以将其注册为一张临时表,然后针对其中的数据进行sql语句
        studentDF.registerTempTable("student");
        
        //针对student临时表执行sql语句,查询年龄大于20岁的学生
        DataFrame df =sqlContext.sql("select * from student where age > 20");
        
        //将查询出来的dataframe再次转换为RDD
        JavaRDD<Row> teenagerRDD = df.javaRDD();
        
        //将RDD中的数据,进行映射,给每个人的年龄,然后映射为student
        JavaRDD<Student> teenagerStudentRDD = teenagerRDD.map(new Function<Row, Student>() {

            private static final long serialVersionUID = 1L;

            @Override
            public Student call(Row row) throws Exception {
                //row中的顺序是按照字典顺序进行排列的
                Student student = new Student();
                student.setAge(row.getInt(0));
                student.setId(row.getInt(1));
                student.setName(row.getString(2));
                return student;
            }
        });
        
        //将数据collect 回来,打印出来
        List<Student> studentList = teenagerStudentRDD.collect();
        for (Student student : studentList) {
            System.out.println(student);
        }
        
    }

}

Scala版本:
package com.spark.spark_sql

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.sql.SQLContext
/**
 * 如果要使用scala开发spark程序
 * 然后在其中还要实现基于反射的RDD到dataframe的转换,就必须得用object  extends App的方式
 * 不能使用def main()方法的方式来运行程序,否则就会报错no typetag for ... class
 */
object RDD2DaframeReflection extends App{
  
    val conf = new SparkConf().setMaster("local").setAppName("RDD2DaframeReflection")
    val sc = new SparkContext(conf)
    val sqlContext = new SQLContext(sc)
    val lines = sc.textFile("e://student.txt", 1)
    
    //在scala中使用反射的方式,进行RDD到Dataframe的转换,需要手动的导入一个隐士转换
    import SQLContext.implicits._
    case class Student(id : Int ,name : String ,age : Int)
    
    //这里其实就是一个普通的,元素是case class 的rdd
    val students = lines.map(line =>line.split(",")).map(arr => Student(arr(0).trim().toInt,arr(1),arr(2).trim().toInt))
    
    //直接使用RDD的toDF,即可将其转换为dataframe
    val studentDF=students.toDF()
    
    //注册为一个临时表
    studentDF.registerTempTable("student")
    
    //
    val teenagerDF = sqlContext.sql("select * from student where age > 20")
    
    val teenagerRDD = teenagerDF.rdd
    
    //在scala中,row中的数据的顺序,反而是按照我们期望的来排列的,这个是跟java是不一样的
    teenagerRDD.map{row => Student(row(0).toString().toInt,row(1).toString(),row(2).toString().toInt )
    
    }.collect().foreach(stu => print(stu.id+ ":" +stu.name+":"+stu.age))
    
    
    //在scala中,对row的使用比java中的row更加的丰富
    //在scala中,可以用row的getAs()方法,获取指定列名的列
    teenagerRDD.map(row =>Student(row.getAs[Int]("id"),row.getAs("name"),row.getAs("age"))).collect()
      .foreach(stu => print(stu.id+ ":" +stu.name+":"+stu.age))
    
      //还可以通过row的getValuesMap,方法,获取指定几列的值,返回的是一个map
    teenagerRDD.map(row => {
      val map = row.getValuesMap(Array("id","name","age"))
      Student(map("id").toString().toInt,map("name").toString(),map("age").toString().toInt)
    }).collect().foreach(stu => print(stu.id+ ":" +stu.name+":"+stu.age))
}




2.通过编程接口来创建dataframe
Java版本:
package com.spark.spark_sql;

import java.util.ArrayList;
import java.util.List;

import org.apache.spark.SparkConf;
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.sql.DataFrame;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RowFactory;
import org.apache.spark.sql.SQLContext;
import org.apache.spark.sql.types.DataType;
import org.apache.spark.sql.types.DataTypes;
import org.apache.spark.sql.types.StructField;
import org.apache.spark.sql.types.StructType;

/**
 * 以编程的方式动态的执行元数据,将RDD转化为dataframe
 * @author Administrator
 *
 */
public class RDD2DataProgrammatically {
public static void main(String[] args) {
    
    //创建sparkconf
    SparkConf conf = new SparkConf().setMaster("local").setAppName("RDD2DataProgrammatically");
    JavaSparkContext sc = new JavaSparkContext(conf);
    SQLContext sqlContext = new SQLContext(sc);
    
    //第一步,创建一个普通的RDD,但是必须将其转换为RDD<Row>的格式
        JavaRDD<String> lines = sc.textFile("e://student.txt");
        
        JavaRDD<Row> rows = lines.map(new Function<String, Row>() {
            private static final long serialVersionUID = 1L;

            @Override
            public Row call(String line) throws Exception {
                String[] split = line.split(",");
                
                return RowFactory.create(Integer.parseInt(split[0]),split[1],Integer.parseInt(split[2]));
            }
        });
        //第二步,动态元数构造据
        //比如说,id name age 等fields的名称和类型都是在程序运行过程中,
        //动态的从MySQL等DB中或者是配置文件中,加载出来的,是不固定的
        //所以特别适合这种编程的方式来构造元数据
        
        List<StructField> fields = new ArrayList<StructField>();
        
        fields.add(DataTypes.createStructField("id", DataTypes.IntegerType, true));
        fields.add(DataTypes.createStructField("name", DataTypes.StringType, true));
        fields.add(DataTypes.createStructField("age", DataTypes.IntegerType, true));
        
        StructType structType = DataTypes.createStructType(fields);
        
        //第三部,使用动态构造元数据,将RDD转换为dataframe
        DataFrame studentDF = sqlContext.createDataFrame(rows, structType);
        
        //后面就可以直接使用这个df了
        //注册临时表
        studentDF.registerTempTable("student");
        DataFrame stus = sqlContext.sql("select * from student where age > 20");
        
        List<Row> list = stus.javaRDD().collect();
        
        for (Row row : list) {
            System.out.println(row);
        }    
}
}
Scala版本:
package com.spark.spark_sql

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.sql.SQLContext
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.StructType
import org.apache.spark.sql.types.StructField
import org.apache.spark.sql.types.IntegerType
import org.apache.spark.sql.types.StringType

object RDD2DataframeProgramatically extends App{
 val conf = new SparkConf().setMaster("local").setAppName("RDD2DataframeProgramatically");
  
  val sc = new SparkContext(conf)
  
  val sqlContext = new SQLContext(sc)
  
  //第一步:构造出元素为Row的普通的RDD
  val studentRDD = sc.textFile("e://student.txt", 1)
      .map(line => Row(
            line.split(",")(0).toInt,
            line.split(",")(1),
            line.split(",")(2).toInt))
  
   //第二步:以编程的方式构造元数据
   val structType =StructType(Array(
       StructField("id",IntegerType,true),
       StructField("name",StringType,true),
       StructField("age",IntegerType,true)))
  
  //第三步:进行RDD到dataframe的转换
       
   val studentDF = SQLContext.createDataFrame(studentRDD,structType )  
   
   studentDF.registerTempTable("student");
  
  val teenagerDF = sqlContext.sql("select * from student where age > 20")
  
   teenagerDF.rdd.collect.foreach(row => println(row))
  

  
}


6.通用的load和save操作
1.dataframe的load和save

对于的spark sql的dataframe来说,无论是从什么数据源创建出来的dataframe,都有一些共同的load和save操作,load操作主要是用于加载数据,创建出来的dataframe:save操作,主要用于将dataframe中的数据集保存到文件中(保存到的是一个目录中)

Java版本:
Dataframe df = sqlContext.read().load(“users.parquet”);
df.select(“name”,”facourite_color”).write().save(“nameAndFav_color_dir”);


Scala版本:
Val df = sqlContext.read.load(“users.parquet”)
Df.select(“name”,”facourite_color”).write().save(“nameAndFav_color_dir”)


Java版本:
package com.spark.spark_sql;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.SQLContext;
/**
 * 通用的load和save操作
 * @author Administrator
 */
public class GenericLoadSave {
    public static void main(String[] args) {
        
    SparkConf conf = new SparkConf().setMaster("local").setAppName("GenericLoadSave");
    JavaSparkContext sc = new JavaSparkContext(conf);
    SQLContext sqlContext = new SQLContext(sc);
    DataFrame usersDF = SQLContext.read().load("C://Users//Administrator//Desktop//users.parquet");
    
    usersDF.printSchema();
    usersDF.show();
    usersDF.select("name","favourite_color").write().save("e://users2");
    }
}

Scala版本:
package com.spark.spark_sql

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.sql.SQLContext
import scala.tools.scalap.Main
import org.apache.spark.sql.DataFrame

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

    val conf = new SparkConf().setAppName("GenericLoadSave")
    val sc = new SparkContext(conf)
    val sqlContext = new SQLContext(sc)
    val  usersDF = sqlContext.read.load("hdfs://hadoop01:8020/spark_input/users.parquet")
    usersDF.select("name","favourite_color").write.save("hdfs://hadoop01:8020/spark_output")

  }
}

2.手动指定数据源:

也可以手动指定用来操作的数据源,数据源通常是使用其权限定名来指定,比如parquet是org.apache.spark.sql.parquet。但是spark sql内置了一些数据源类型,比如json,parquet.jdbc等等,实际上,通过这个功能,就可以在不同类型的数据源之间进行转换了。比如将json文件中的数据存储到parquet文件中。默认情况下,如果不指定数据源,默认就是parquet
Java版本:
DataFrame df =SQLContext.read().format(“json”).load(“people,json”)
Df.select(“name”,”age”).write().format(“parquet”).save(“out”)

Scala版本:
Val df = SQLContext.read.format(“json”).load(“people,json”)
Df.select(“name”,”age”).write.format(“parquet”).save(“out”) 


Java版本
     
package com.spark.spark_sql;

import org.apache.spark.SparkConf;
import org.apache.spark.SparkContext;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.SQLContext;

/**
 * 手动指定数据源
 * @author Administrator
 */
public class ManuallySpecifyOptions {
    public static void main(String[] args) {
        
        SparkConf conf = new SparkConf().setMaster("local").setAppName("ManuallySpecifyOptions");
        
        SparkContext sc = new SparkContext(conf);
        
        SQLContext SQLContext = new SQLContext(sc);
        
        DataFrame df = SQLContext.read().format("json").load("e://users.parquet");
        
        df.write().format("json").save("e://out");
        
    }
}


3.Save mode 

Spark sql 对于save操作,提供了不同的save mode.主要用来处理,当目标位置已经有数据时,应该如何处理。而且save操作并不会执行锁操作,并且不是原子的,因此是有一定风险出现脏数据的

Save mode    意义
SaveMode.ErrorIfExists(默认)    如果目标位置存在数据,那么就抛出异常
SaveMode.Append    如果目标位置存在数据,那么就将数据追加进去
SaveMode.Overwrite    如果目标位置存在数据,那么就将已经存在的数据删除,用新的数据进行覆盖
SaveMode.Ignore    如果目标位置存在数据,那么就忽略,不做任何的操作
Java版本:
package com.spark.spark_sql;
import org.apache.derby.impl.tools.sysinfo.Main;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.SQLContext;
import org.apache.spark.sql.SaveMode;
/**
 * savemode 示例
 * @author Administrator
 */
public class SaveModeDemo {
    public static void main(String[] args) {
        SparkConf conf = new SparkConf().setMaster("local").setAppName("SaveModeDemo");
        JavaSparkContext sc = new JavaSparkContext(conf);
        SQLContext sqlContext = new SQLContext(sc);
        DataFrame df = sqlContext.read().format("json").load("e://users.parquet");
        df.save("e://out", SaveMode.Append);

    }
}

三、Parquet数据源
1.使用编程方式加载数据
Parquet是面向分析型业务的列式存储格式,由Twitter和cloudera合作开发。2015年成为Apache的顶级项目

列式存储和行式存储相比较有哪些优点;
1.可以跳过不符合条件的数据,只读取需要的数据,降低IO数据量
2.压缩编码可以降低磁盘存储空间。由于同一列的数据类型是一样的,可以使用更高效的压缩编码(例如Run Length Encoding 和 Delta Encoding)进一步节约磁盘空间。
3.只读取需要的列,支持向量运算,能够获取更好的扫描性能
Java版本:

package com.spark.spark_sql;

import java.util.List;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SQLContext;

/**
 * parquet数据源之使用编程方式加载数据
 */
public class ParquetLoadData {

    public static void main(String[] args) {
        SparkConf conf = new SparkConf().setMaster("local").setAppName("ParquetLoadData");
        JavaSparkContext sc = new JavaSparkContext(conf);
        SQLContext sqlContext = new SQLContext(sc);
        //读取parquet文件中的数据,创建一个dataframe
        DataFrame userDF = sqlContext.read().parquet("e://users.parquet");
        
        //将其注册为临时表,使用sql查询所需要的数据
        userDF.registerTempTable("users");
        
        DataFrame userNamesDF = sqlContext.sql("select name from users");
        
        //对查询出来的dataframe进行transformation操作,处理数据,然后打印出来
        List<String> userNames = userNamesDF.javaRDD().map(new Function<Row, String>() {

            private static final long serialVersionUID = 1L;

            @Override
            public String call(Row row) throws Exception {
                
                return "Name: "+row.getString(0);
            }
        }).collect();
        
        for (String name : userNames) {
            System.out.println(name);
        }
    }
}



Scala版本:
package com.spark.spark_sql

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.sql.SQLContext

object PatquetLoadData {
  
  val conf = new SparkConf().setAppName("PatquetLoadData")
  val sc = new SparkContext(conf)
  val sqlContext =new SQLContext(sc)
  val usersDF = sqlContext.read.parquet("hdfs://hadoop01:8020/spark_input/users.parquet");
  usersDF.registerTempTable("user");
  val userNameDF = sqlContext.sql("select * from user")
  userNameDF.rdd.map(row => "Name: "+row(0)).collect.foreach(userName =>println(userName))
}
2.自动分区推断
表分区是一张常见的优化的方式,比如hive中就提供分区表的特性。在一个分区表中,不同分区的数据通常存储在不同的目录中,分区列的值通常就包含在了分区的目录名中。Spark sql中的parquet数据源,支持自动根据目录名推断出分区信息。例如,如果将入口数据存储在分区表中,并且使用性别和国家作为分区列。那么目录结构可能是如下所示:

|----tableName
|----gender=male
  |----country=US
          |.....
  |----country=ZH
|----gender=female
  |--country=...
           |...
如果将tableName传入SQLContext.read.parquet()或者SQLContext.read.load()方法,那么sparksql就会自动根据目录的结构,推断出分区的信息,是gender和country。即使数据文件中包含两列的值name和age,但是sparksql返回的dataframe,调用printSchema()方法时,会打印出四个列的值:name age country gender .这就是自动分区推断的功能

此外,分区的列的数据类型,也是自动被推断出来的。目前,spark sql仅支持自动推断出数字类型和字符串类型。有时,用户也许不希望spark sql自动推断分区列的数据类型。此时只要设置一个配置即可,spark.sql.sources.partitionColumnTypeInference.enabled,默认是true,即自动推断分区列的类型,设置为false,即不会自动推断类型。进行自定推断分区列的类型时,所有的分区列的类型,就统一默认的是String

案列:
1.hdfs上创建相对应的目录结构:
bin/hdfs dfs -mkdir -p /spark_input/gender=male/country=US
2.将文件上传到目录下
bin/hdfs dfs -put users.parquet /spark_input/gender=male/country=US/

3.查询出schema
package com.spark.spark_sql;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.SQLContext;
/**
 *parquet数据源之 自动推断分区
 */
public class ParquetPartitionDiscovery {
    public static void main(String[] args) {
        SparkConf conf = new SparkConf().setAppName("ParquetPartitionDiscovery").setMaster("local");
        JavaSparkContext sc = new JavaSparkContext(conf);
        SQLContext sqlContext = new SQLContext(sc);
        DataFrame userDF = sqlContext.read().parquet("hdfs://hadoop01:8020/spark_input/gender=male/country=US/users.parquet");
        userDF.printSchema();
         userDF.show();
    }
}


4.结果:
root
 |-- name: binary (nullable = true)
 |-- favourite_color: binary (nullable = true)
 |-- gender: string (nullable = true)
 |-- country: string (nullable = true)

+----------------+-------------------+------+-------+
|            name|    favourite_color|gender|country|
+----------------+-------------------+------+-------+
|      [6C 65 6F]|         [72 65 64]|  male|     US|
|   [6A 61 63 6B]|[79 65 6C 6C 6F 77]|  male|     US|
|[6B 69 74 74 79]|   [77 68 69 74 65]|  male|     US|
|      [74 6F 6D]|   [67 72 65 65 6E]|  male|     US|
|      [61 6C 6C]|      [70 69 6E 6B]|  male|     US|
+----------------+-------------------+------+-------+
【注意】前面是因为parquet数据的问题,自己从hive中导出来的。可以利用json格式的数据load,然后save的时候以parquet的方式生成一个parquet文件,用于测试

3.合并元数据
如同ProtocolBuffer,Avro,Thrift一样,Parquet也是支持元数据合并的。用户可以一开始就定义一个简单的元数据,然后随着业务需要。逐渐往元数据中添加更多的列,在这种情况下,用户可能会创建多个parquet文件,有着多个不同的却互相兼容的元数据。Parquet数据源支持自动推断这种情况,并且进行多个parquet文件的元数据的合并

因为元数据合并是一种相对耗时的操作,而且在大多数的情况下不是一种必要的特性,从spark1.5.0版本开始,默认是关闭parquet文件的自动合并元数据的。可以通过以下的两种方式开启parquet数据源的自动合并元数据的特性

1.读取parquet文件时,将数据源的选项,mergeSchema,设置为true

2.根据sqlContext.setConf()方法,将spark.paquet.mergeSchema参数设置为true

案例:
合并学生的基本信息和成绩信息的元数据
package com.spark.spark_sql

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.sql.SQLContext
import org.apache.spark.sql.SaveMode

object ParquetMergeSchema {
  def main(args: Array[String]): Unit = {
    
 
  val conf = new SparkConf().setAppName("ParquetMergeSchema")
  val sc = new SparkContext(conf)
  val sqlContext = new SQLContext(sc)
  
  import SQLContext.implicits._
  //创建一个dataframe,作为学生的基本信息,并写入一个parquet文件中
  val studentsWithNameAge =Array(("leo",23),("jack",25))
  
  val studentsWithNameAgeDF = sc.parallelize(studentsWithNameAge, 2).toDF("name","age")
  studentsWithNameAgeDF.save("hdfs://hadoop01:8020/spark_out/students","parquet", SaveMode.Append)
  
  //创建一个dataframe,作为学生的成绩信息,并写入一个parquet文件中
   val studentsWithNameGrade =Array(("marry","A"),("jack","B"))
  
  val studentsWithNameGradeDF = sc.parallelize(studentsWithNameGrade, 2).toDF("name","age")
  studentsWithNameGradeDF.save("hdfs://hadoop01:8020/spark_out/students","parquet", SaveMode.Append)
  
  
  //首先,第一个dataframe和第二个dataframe的元数据肯定是不一样的
  //一个包含了name和age两个列,一个是包含name和grade两个列
  //所以,这期望的是,读取出来的表数据,自动合并两个文件的元数据,出现三列,name age grade 
  
  
  //用mergeSchema的方式,读取students表中的数据,进行元数据的合并
  val studentsDF = sqlContext.read.option("mergeSchema", "true").parquet("hdfs://hadoop01:8020/spark_out/students")
  
  studentsDF.printSchema();
  studentsDF.show();
  
  } 
}
四、JSON数据源
Spark sql 可以自动推断JSON文件的元数据,并且加载其数据,创建一个dataframe。可以使用SQLContext.read.json()方法,针对一个元素为String的RDD,或者是一个JSON文件

但是要注意的是,这里使用的JSON文件与传统意义上的JSON文件是不一样的。每行都必须也只能包含一个,单独的,自包含的,有效的JSON对象。不能让json对象分散在多行。否则会报错

综合性复杂案例:查询成绩为80分以上的学生的基本信息与成绩信息
Java 版本
package com.spark.spark_sql;

import java.util.ArrayList;
import java.util.List;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
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.api.java.function.PairFunction;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RowFactory;
import org.apache.spark.sql.SQLContext;
import org.apache.spark.sql.SaveMode;
import org.apache.spark.sql.types.DataTypes;
import org.apache.spark.sql.types.StructField;
import org.apache.spark.sql.types.StructType;

import scala.Tuple2;
/**
 * json数据源
 */
public class JSONDataSource {

    public static void main(String[] args) {
        SparkConf conf = new SparkConf().setAppName("JSONDataSource").setMaster("local");
        JavaSparkContext sc = new JavaSparkContext(conf);
        SQLContext sqlContext = new SQLContext(sc);

        // 针对json文件,创建出dataframe
        DataFrame studentDF = sqlContext.read().json("e://student.json");    //hdfs://hadoop01:8020/spark_input/student.json
        // 针对学生成绩信息的dataframe,注册临时表,查询分数大于80分的学生的姓名和分数
        studentDF.registerTempTable("student");

        DataFrame stuNameDF = sqlContext.sql("select name,score from student where score > 80");

        List<String> goodName = stuNameDF.javaRDD().map(new Function<Row, String>() {

            private static final long serialVersionUID = 1L;

            @Override
            public String call(Row row) throws Exception {

                return row.getString(0);
            }
        }).collect();

        // 针对JavaRDD<String> 创建dataframe
        List<String> studentInfoJSONs = new ArrayList<String>();
        studentInfoJSONs.add("{\"name\":\"leo\",\"age\":18}");
        studentInfoJSONs.add("{\"name\":\"marry\",\"age\":15}");
        studentInfoJSONs.add("{\"name\":\"jack\",\"age\":30}");
        JavaRDD<String> studentInfoJSONsRDD = sc.parallelize(studentInfoJSONs);

        DataFrame studentInfoDF = sqlContext.read().json(studentInfoJSONsRDD);

        // 针对学生的基本信息的dataframe,注册临时表,然后查询分数大于80分的学生的基本信息

        studentInfoDF.registerTempTable("info");
        String sql = "select name , age  from  info where name in (";

        for (int i = 0; i < goodName.size(); i++) {
            sql += "'" + goodName.get(i) + "'";
            if (i < goodName.size() - 1) {
                sql += ",";
            }
        }
        sql += ")";
        DataFrame goodStuInfosDF = sqlContext.sql(sql);

        // 将两份数据的dataframe转换为javapairRDD,执行join transformation

        JavaPairRDD<String, Tuple2<Integer, Integer>> pairs = goodStuInfosDF.javaRDD()
                .mapToPair(new PairFunction<Row, String, Integer>() {

                    private static final long serialVersionUID = 1L;

                    @Override
                    public Tuple2<String, Integer> call(Row row) throws Exception {

                        return new Tuple2<String, Integer>(row.getString(0), Integer.valueOf((int) row.getLong(1)));
                    }
                }).join(stuNameDF.javaRDD().mapToPair(new PairFunction<Row, String, Integer>() {

                    private static final long serialVersionUID = 1L;

                    @Override
                    public Tuple2<String, Integer> call(Row row) throws Exception {
                        return new Tuple2<String, Integer>(row.getString(0), Integer.valueOf((int) row.getLong(1)));
                    }
                }));
        
        //将封装在RDD中好学生的全部信息,转换为一个javaRDD<Row>的格式
        JavaRDD<Row> rows = pairs.map(new Function<Tuple2<String,Tuple2<Integer,Integer>>, Row>() {

            private static final long serialVersionUID = 1L;

            @Override
            public Row call(Tuple2<String, Tuple2<Integer, Integer>> t) throws Exception {
                
                return RowFactory.create(t._1,t._2._2,t._2._1);
            }
        });
        
        //创建一份元数据,将JavaRDD<Row>转换为dataframe
        List<StructField> fields = new ArrayList<StructField>();
        fields.add(DataTypes.createStructField("name", DataTypes.StringType, true));
        fields.add(DataTypes.createStructField("score", DataTypes.IntegerType, true));
        fields.add(DataTypes.createStructField("age", DataTypes.IntegerType, true));
        StructType structType = DataTypes.createStructType(fields);
        DataFrame goodStudentsDF =sqlContext.createDataFrame(rows, structType);
                
        //将好学生的全部信息保存到json文件中去
        goodStudentsDF.write().format("json").save("e://good");    //hdfs://hadoop01:8020/spark_out/goodStudent
        
    }
}

 
Scala版本:
package com.spark.spark_sql

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.sql.SQLContext
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.StructType
import org.apache.spark.sql.types.StructField
import org.apache.spark.sql.types.StringType
import org.apache.spark.sql.types.IntegerType

object JSONDatasource {
  val conf = new SparkConf().setAppName("JSONDatasource")
  val sc = new SparkContext(conf)
  val sqlContext = new SQLContext(sc)
  val studentScoreDF =sqlContext.read.json("hdfs://hadoop01:8020/spark_input/student.json")
  studentScoreDF.registerTempTable("stu_score")
  val goodStuScoreDF = sqlContext.sql("select name , score from stu_score where score > 80")
  
  val goodStuNames = goodStuScoreDF.map(row => row(0)).collect()
  
  
  val studentInfoJSON =Array("{\"name\":\"Leo\",\"age\":18}","{\"name\":\"marry\",\"age\":15}","{\"name\":\"jack\",\"age\":30}") 
  
  
  val studentInfoRDD =sc.parallelize(studentInfoJSON, 3)
  
  val studentInfoDF = sqlContext.read.json(studentInfoRDD)//json格式特有的,可以接受RDD
  
  studentInfoDF.registerTempTable("stu_info")
  
  var sql = "select name , age from stu_info where name in ("
  
  for(i <- 0 until goodStuNames.length){
    
    sql += "'"+goodStuNames(i)+"'"
    if(i < goodStuNames.length-1){
      sql += ","
    }
    
  }
  sql += ")"
  
  val goodStuInfoDF = sqlContext.sql(sql)
  
  val goodStuRDD = goodStuInfoDF.rdd.map{row => (row.getAs[String]("name"),row.getAs[Long]("age"))}.join (goodStuScoreDF.rdd.map{row => (row.getAs[String]("name"),row.getAs[Long]("score"))})
  
  val goodStuRows = goodStuRDD.map(info => Row(info._1,info._2._2.toInt,info._2._1.toInt))
  
  val structType = StructType(Array(StructField("name",StringType,true),StructField("score",IntegerType,true),StructField("age",IntegerType,true)))
  
  val goodStudentDF = sqlContext.createDataFrame(goodStuRows, structType)
  
  goodStudentDF.write.format("json").save("hdfs://hadoop01:8020/spark_out/good_scala")
  
  
}


五、Hive数据源
Spark sql 支持对hive中存储的数据进行读写,操作hive中的数据时,就必须创建HiveContext,而不是SQLContext。HiveContext继承SQLContext,但是增加了在hive元数据库中查找表,以及用hiveql语法编写SQL的功能。处理sql()方法外,HiveContext还提供hql()方法,从而用hive语法来编译sql。

使用HiveContext,可以执行Hive的大部分功能,包括创建表、往表里导入数据以及用SQL语句查询表中的数据,查询出来的数据是一个row数组

将hive-site.xml拷贝到spark/conf目录下,将mysql connector拷贝到spark/lib目录下

HiveContext sqlContext = new HiveContext(sc);
sqlContext.sql(“create table if not exists student (name string ,age int)”)
sqlContext.sql(“load data local inpath ‘/usr/local/student.txt’into table students”);
Row[] teenagers = sqlContext.sql(“select name ,age from students where age <= 18”).collect();


将数据保存到表中
Spark sql还允许将数据保存到hive表中。调用Dataframe的saveAsTable,即可将dataframe中的数据保存到hive表中。与registerTempTable不同,saveAsTable是会将dataframe汇总的数据物化到hive表中,而且还会在hive元数据库中创建表的元数据

默认情况下,saveAsTable会创建一张hive managed table,也就是说,数据的位置都是有元数据库中的信息控制的。当managed table 被删除时,表中的数据也会一并内物理删除

RegisterTempTable只是注册一个临时的表,只要spark application重启或者停止了,那么表就没有了。而SaveAsTable创建的是物化的表,无论是spark application重启还是停止,表都会一直存在

调用HiveContext.table()方法,还可以直接针对hive中的表,创建一个dataframe

案例:查询分数大于80分的学生的信息
package com.spark.spark_sql;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.hive.HiveContext;
/**
 * hive数据源
 * @author Administrator
 */
public class HiveDataSource {

    public static void main(String[] args) {
        SparkConf conf = new SparkConf().setAppName("HiveDataSource");
        JavaSparkContext sc = new JavaSparkContext(conf);
        //创建HiveContext,注意,这里接收的是sparkContext作为参数,不是Javasparkcontext
        HiveContext hiveContext = new HiveContext(sc.sc());
        
        //第一个功能,使用HiveContext的sql()/hql()方法,可以执行hive中可以执行的hiveQL语句
        //判断是否存在student_info表,如果存在则删除
        hiveContext.sql("drop  table if exists student_info");
        //判断student_info表是否不存在,不存在就创建该表
        hiveContext.sql("create  table if not exists student_info(name string,age int) row format delimited fields terminated by '\t'");
        
        //将学生基本信息导入到Student_info表
        hiveContext.sql("load data local inpath '/home/hadoop/student_info.txt' into table student_info");
        //用同样的方式向student_scores导入数据
        hiveContext.sql("drop  table if exists student_score");
        
        hiveContext.sql("create  table if not exists student_score(name string,score int)row format delimited fields terminated by '\t'");
        
        hiveContext.sql("load data local inpath '/home/hadoop/student_score.txt' into table student_score");
        
        //第二个功能个,执行sql还可以返回dataframe,用于查询
        
        //执行sql查询,关联两种表,查询成绩大于80 分的学生信息
        DataFrame goodstudentDF  = hiveContext.sql("select i.name,i.age,s.score from student_info i join student_score s on i.name=s.name where s.score >=80");
        
        //第三个功能,可以将dataframe中的数据,理论上来说,dataframe对应的RDD的元素是ROW就可以
        //将Dataframe中的数据保存到hive表中
        
        //将dataframe中的数据保存到good_student_info表中
        hiveContext.sql("drop  table if exists good_student_info");
        goodstudentDF.saveAsTable("good_student_info");
        
        //第四个功能,可以使用table()方法,针对hive表直接创建dataframe
        
        //针对good_student_info表,直接创建dataframe
        Row[] rows = hiveContext.table("good_student_info").collect();
        for (Row row : rows) {
            System.out.println(row);
        }
        
        sc.close();
    }
}



Scala版本:
package com.spark.spark_sql
import org.apache.spark.SparkConf
import org.apache.spark.sql.hive.HiveContext
import org.apache.spark.sql.DataFrame
import org.apache.spark.sql.Row
import org.apache.spark.sql.DataFrame
import org.apache.spark.SparkContext

object HiveDataSource {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("HiveDataSource");
        val sc = new SparkContext(conf)
        //创建HiveContext,注意,这里接收的是sparkContext作为参数,不是Javasparkcontext
        val hiveContext = new HiveContext(sc);
        
        //第一个功能,使用HiveContext的sql()/hql()方法,可以执行hive中可以执行的hiveQL语句
        //判断是否存在student_info表,如果存在则删除
        hiveContext.sql("drop  table if exists student_info");
        //判断student_info表是否不存在,不存在就创建该表
        hiveContext.sql("create  table if not exists student_info(name string,age int) row format delimited fields terminated by '\t'");
        
        //将学生基本信息导入到Student_info表
        hiveContext.sql("load data local inpath '/home/hadoop/student_info.txt' into table student_info");
        
        //用同样的方式向student_scores导入数据
        hiveContext.sql("drop  table if exists student_score");
        
        hiveContext.sql("create  table if not exists student_score(name string,score int)row format delimited fields terminated by '\t'");
        
        hiveContext.sql("load data local inpath '/home/hadoop/student_score.txt' into table student_score");
        
        //第二个功能个,执行sql还可以返回dataframe,用于查询
        
        //执行sql查询,关联两种表,查询成绩大于80 分的学生信息
        val goodstudentDF  = hiveContext.sql("select i.name,i.age,s.score from student_info i join student_score s on i.name=s.name where s.score >=80");
        
        //第三个功能,可以将dataframe中的数据,理论上来说,dataframe对应的RDD的元素是ROW就可以
        //将Dataframe中的数据保存到hive表中
        
        //将dataframe中的数据保存到good_student_info表中
        hiveContext.sql("drop  table if exists good_student_info");
        goodstudentDF.saveAsTable("good_student_info");
        
        //第四个功能,可以使用table()方法,针对hive表直接创建dataframe
        
        //针对good_student_info表,直接创建dataframe
        val rows = hiveContext.table("good_student_info").collect();
        for ( row <- rows) {
            println(row);
        }
  }
}

六、内置函数
在spark 1.5.x版本,增加了一系列内置函数到dataframe API中,并且实现了code-generation的优化。与普通的函数不同,dataframe的函数并不会执行后立即返回一个结果值,而是返回一个Column对象,用于在并行作业中进行求值。Column可以用在dataframe的操作之中,比如select, filter,groupBy等操作。函数的输入值,也可以是Column。

种类    函数
聚合函数    approxCountDistinct,avg,count,
countDistinct,first,last,max,mean,min,sum,sumDistinct
集合函数    Array_contains,explode,size,sort_array
日期/时间函数    日期时间转换
Unix_timestamp,from_unixtime,to_date
Quarter,day,dayofyear,weekofyear,
From_utc_timestamp,to_utc_timestamp
从日期中提取字段
Year,month,dayofmonth,hour,minute,
second
日期/时间函数    日期时间计算
Dateiff,date_add,date_sub,add_months,
last_day,next_day,months_between
获取当前时间
Current_date,current_timestamp,trunc,
date_format
数学函数    Abs,scros,asin,atan,atan2,bin,cbrt,
ceil,conv,cos,sosh,exp,expm1,factorial,floor,hex,hypot,log,log10,log1p,log2,
Pmod,pow,rint,round,shiftLeft,
shiftRight,shiftRightUnsigned,sifnum,
Sin,sinh,sqrt,tan,tanh,toDegrees,
toRadians,unhex
混合函数    Array,bitwiseNOT,callUDF,coalesce,
crc32,greatest,if,inputFileName,isNaN,
isnotnull,isnull,least,lit,md5,
monotonicalliIncreasingId,nanvl,negate,not,rand,randn,randn
sha,sha1,sparkPartitionId,struct,when
字符串函数    Ascii,base64,concat,concat_ws,decode,encode,format_number,format_string,get_json_object,initcap,instr,length,levenshtein,locate,lower,lpad,ltrim,printf,redexp_extract,regexp_replace,repeat,reverse,rpad,rtrim,soundex,space,split,substring,substring_index,translate,trim,unbase64,upper
窗口函数     cumeDist,denseRank,lag,lead,ntile,percentRank,rank,rowNumber

案例:
根据每天的用户访问和购买日志,统计每日的UV和销售额

统计每日的UV
package com.spark.spark_sql

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.sql.SQLContext
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.StructType
import org.apache.spark.sql.types.StructField
import org.apache.spark.sql.types.StringType
import org.apache.spark.sql.types.IntegerType
import org.apache.spark.sql.functions._

object DailyUV {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("DailyUV")
    val sc = new SparkContext(conf)
    val sqlContext = new SQLContext(sc)
    //构造用户 访问日志数据,并创建dataframe
    //要使用spark内置函数,就必须在这里代入sparkcontext下的隐式转换
    import sqlContext.implicits._
    //模拟用户访问日志,日志用逗号隔开,第一列是日期,第二列是用户id
    val userAccessLog = Array("2015-10-01,1122","2015-10-01,1122", "2015-10-01,1123", "2015-10-01,1125","2015-10-01,1125", "2015-10-02,1122", "2015-10-02,1124","2015-10-02,1122",  "2015-10-02,1123")
    val userAccessLogRDD= sc.parallelize(userAccessLog, 1);
    //将模拟出来的用户日志RDD,转换为dataframe
    //首先将普通的RDD转换为row的RDD
    val userAccessLogRowRDD = userAccessLogRDD.map(log =>Row(log.split(",")(0),log.split(",")(1).toInt))
  
    
    //构造dataframe元数据
    val structType = StructType(Array(StructField("date",StringType,true),StructField("userid",IntegerType,true)))
    val userAccessLogRowDF=sqlContext.createDataFrame(userAccessLogRowRDD, structType)
    
    //这里正式使用spark1.5.x版本的提供的最新特性,内置函数,countDistinct
    //每天都有很多用户来访问,但是每个用户可能每天都会访问很多次
    //UV:指的是对用户进行去重以后的访问总数
    //聚合函数的用法;
    //首先对dataframe调用groupBy()方法,对某一列进行分组
    //然后调用agg()方法,第一个参数,必须必须传入之前在groupBy方法中出现的字段
    //第二个参数,传入countDistinct、sum,first等,spark提供的内置函数
    //内置函数中,传入的参数也是用单引号作为前缀的,其他的字段
    
    
    userAccessLogRowDF.groupBy("date").agg('date, countDistinct('userid)).map(row => Row(row(1),row(2))).collect.foreach(println)
        
  }
}


统计每日的销售额
package com.spark.spark_sql

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.sql.SQLContext
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.StructType
import org.apache.spark.sql.types.StructField
import org.apache.spark.sql.types.StringType
import org.apache.spark.sql.types.DoubleType
import org.apache.spark.sql.functions._

object DailySale {
  
  val conf = new SparkConf().setMaster("local").setAppName("DailySale")
  val sc = new SparkContext(conf)
  val sqlContext = new SQLContext(sc)
  import sqlContext.implicits._
  
  //模拟元数据
  //实际上,有些时候,会出现日志的上报错误,比如日志里丢了用户的信息,那么这种,就一律不统计了
  val userSaleLog = Array("2015-10-01,100,1122","2015-10-01,100,1133","2015-10-01,100,","2015-10-02,300,1122","2015-10-02,200,1122","2015-10-02,100,","2015-10-02,100,1122")

  val userSaleLogRDD = sc.parallelize(userSaleLog, 1);
  
  //进行过滤
  
  val filteredUserSaleRowRDD = userSaleLogRDD.filter(log => if(log.split(",").length ==3) true else false)
  
  
  val userSaleLogRowRDD = filteredUserSaleRowRDD.map(log => Row(log.split(",")(0),log.split(",")(1).toDouble))
  
  val structType = StructType(Array(StructField("date",StringType,true),StructField("sale_amount",DoubleType,true)))
  
  val userSaleLogDF = sqlContext.createDataFrame(userSaleLogRowRDD, structType)
  //开始统计每日销售额的统计
  userSaleLogDF.groupBy("date").agg('date, sum('sale_amount)).map(row => Row(row(1),row(2))).collect().foreach(println)
  
  
}
七、开窗函数
Spark1.4.x版本以后,为spark sql和dataframe引入了开窗函数,比如最经典最常用的row_number(),可以让我们实现分组取topN的逻辑

案例:统计每个种类的销售额排名前3 的产品

package com.spark.spark_sql;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.hive.HiveContext;

/**
 * 开窗函数row_number
 * @author Administrator
 */
public class RowNumberWindowFunction {
public static void main(String[] args) {
    
    SparkConf conf = new SparkConf().setAppName("NewNumberWindowFunction");
    JavaSparkContext sc = new JavaSparkContext(conf);
    HiveContext hiveContext = new HiveContext(sc.sc());
    
    //创建销售表sales表
    hiveContext.sql("drop table if exists sales");
    hiveContext.sql("create table if not exists sales (product string,category string ,revenue bigint) row format delimited fields terminated by '\t'");
    hiveContext.sql("load data local inpath '/home/hadoop/sales.txt' into table sales");
    //开始编写我们的统计逻辑,使用row_number()开窗函数
    //先说明一下,row_number()开窗函数的作用
    //其实就是给每一个分组的数据,按照其排序顺序,打上一个分组内的行号
    //比如说,有一个分组date=20151001,里面有三条数据,1122 1121 1124 
    //那么对这个分组的每一行使用row_number()开窗函数以后,三行依次会获得一个组内的行号
    //行号从1开始递增,比如 1122 11121 21124 3
    
    DataFrame top3SalesDF = hiveContext.sql("select product,category,revenue from "
            + "(select product,category,revenue ,row_number() over (partition by category order by revenue desc ) rank from sales) tmp_sales  where rank <=3");
    
    //row_number()开窗函数的语法说明
    //首先,可以在select查询时,使用row_number()函数
    //其次,row_number()函数后面先跟上over关键字
    //然后括号中是partition by 也就是根据那个字段进行分组
    //其次是可以使用order by进行组内排序
    //然后row_number()就可以给每一个组内的行,一个组一个行号
    
    //将每组排名前三的数据,保存到一个表中
    hiveContext.sql("drop table if exists top3_sales");
    top3SalesDF.saveAsTable("top3_sales");
    sc.close();
}
}

函数名(列) OVER(选项) 
八、UDF自定义函数
UDF:User Defined Function.用户自定义函数
package com.spark.spark_sql

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.sql.SQLContext
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.StructType
import org.apache.spark.sql.types.StructField
import org.apache.spark.sql.types.StringType

object UDF {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("UDF")
    val sc = new SparkContext(conf)
    val sqlContext = new SQLContext(sc)
    
    //构造模拟数据
    val names =Array("Leo","Marry","Jack","Tom")
    val namesRDD = sc.parallelize(names, 1)
    val namesRowRDD = namesRDD.map(name => Row(name))
    val structType =StructType(Array(StructField("name",StringType)))
    val namesRowDF =sqlContext.createDataFrame(namesRowRDD, structType)
    
    //注册一张name表
    namesRowDF.registerTempTable("names")
    
    //定义和注册自定义函数
    //定义函数
    //注册函数
    sqlContext.udf.register("strLen",(str:String) => str.length)
    
    //使用自己的函数
    sqlContext.sql("select name,strLen(name) from names").collect.foreach(println)

  }
}
九、UDAF自定义聚合函数
UDAF:User Defined Aggregate Function。用户自定义聚合函数,是spark1.5.x引入的最新特性。
UDF针对的是单行输入,返回一个输出,
UDAF,则可以针对多行输入,进行聚合计算,返回一个输出,功能更加的强大

Scala代码:
import org.apache.spark.sql.Row
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types._


class StringCount extends UserDefinedAggregateFunction {
  //inputschema   指的是,输入数据的数据
  override def inputSchema: StructType = {
    StructType(Array(StructField("str",StringType,true)))
  }
  //bufferSchema,指的是,中间进行聚合时,所处理的数据类型
  override def bufferSchema: StructType = {
    StructType(Array(StructField("count",IntegerType,true)))
  }

  //dataType,指的是,函数的返回值的类型
  override def dataType: DataType = {
   IntegerType
  }
  override def deterministic: Boolean = {
    true
  }
  //为每一个分组的数据执行初始化操作
  override def initialize(buffer: MutableAggregationBuffer): Unit = {
    buffer(0)=0
  }
  //指的是,每个分组,有新的值进来的时候,如何进行分组,对应的聚合值的计算
  override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
    buffer(0)=buffer.getAs[Int](0)+1

  }
  //由于spark是分布式的,所以一个分组的数据,可能会在不同的节点上进行局部的聚合,就是update
  //但是,最后一个分组,在各个节点上的聚合值,要执行merge,也就是合并
  override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
    buffer1(0)=buffer1.getAs[Int](0)+buffer2.getAs[Int](0)
  }
  //最后,指的是,一个分组的聚合,如何通过中间的缓存聚合值,随后返回一个最终的聚合值
  override def evaluate(buffer: Row): Any = {
    buffer.getAs[Int](0)
  }
}


十、工作原理以及性能调优

1.工作原理
Sqlparse、Analyser、Optimizer、SparkPlan



2.性能调优
1.设置shuffle过程中的并行度spark.sql.shuffle.partitions(sqlContext.setConf())
2.在hive数据仓库建设过程中,合理设置数据类型,比如能设置为int的就不要设置为bigint.减少数据类型导致的不必要的内存开销
3.编写sql时,尽量给出明确的列名,比如select name from students。不要写select * 的方式
4.并行处理查询结果:对于spark sql查询的结果,如果数据量比较大的话,比如超多1000条,那么就不要一次collect到driver在处理。使用foreach()算子,并行处理查询结果
5.缓存表:对于一条sql语句中可能多次使用到表,可以对其进行缓存没使用sqlContext.cache Table(tableName),或者DataFrame.cache()即可spark sql会用内存列存储的格式进行表的缓存。然后将spark sql就可以仅仅扫描需要使用的列,并且自动优化压缩,来最小化内存使用和GC开销。sqlcontext.uncacheTable(tableName)可以将表从缓存中移除。用sqlcontext.setConf(),设置spark.sql.inMemoryColumnarStorage.batchSize参数(默认10000),可以配置列存储单位

6.广播join表:spark.sql.autoBroadcastJoinThreshold.默认是10485760(10MB),也就是在10M以内的表将其广播出去,在内存足够用的情况下,增加其大小,让更多的表广播出去,就可以将join中的较小的表广播出去,而不是进行网络数据传输了。
7.钨丝计划:spark.sql.tungsten.enable,默认是true 自动管理内存

最有效的就是第四点,缓存表和广播join表也是非常不错的



十一、Hive on spark 
背景:
Hive是目前大数据领域,事实上的sql标准。其底层默认是基于MapReduce实现的。但是由于mapreduce速度是在比较慢。因此这两年,陆续出来了新的sql查询引擎。包括spark sql,hive on Taz ,hive on spark 

Spark sql 与hive on spark 是不一样的,spark sql 自己研发出来的针对各种数据源,包括hive、json、parquet、JDBC、RDD等都可以执行查询的,一套基于spark计算引擎的查询引擎。因此它是spark的一个项目,只不过提供了针对hive执行查询的功能而已。适合在一些使用spark技术栈的大数据应用类系统中使用。

而hive on spark是hive 的一个项目,他是指,不通过mapreudce作为唯一的引擎,而是将spark作为底层的查询引擎,hive on spark ,只适用于hive,在可预见的未来,很有可能hive默认的底层引擎从mapreduce切换为spark,适合于将原有的hive数据仓库以及数据分析替换为spark引擎,作为公司通用大数据统计计算分析引擎

Hive基本的工作原理:
hiveQL语句==>
语法分==>AST==>
生成逻辑执行计划==>operator Tree==>
优化逻辑执行计划==>optimized operator Tree==>
生成物理执行计划==>Task Tree==>
优化物理执行计划==>Optimized Task Tree==>
执行优化后的Optimized  Task Tree



Hive on spark 的计算原理有如下的几个要点:
1.将hive表作为spark RDD来进行操作

2.使用hive 原语
对于一些针对RDD的操作,比如,groupByKey(),sortByKey()等等。不使用spark的transformation操作和原语。如果那样做的话,那么就需要重新实现一套hive的原语,而且hive增加了新功能,那么又要实现新的spark 原语。因此,选择将hive的原语包装为针对RDD的操作即可

3.新的物理执行计划生成机制
使用sparkCompiler将逻辑执行计划,集=即Operator Tree,转换为Task tree。提交spark task给spark进行执行。Sparktask包装了DAG,DAG包装为sparkwork,Sparktask根据sparkwork表示的DAG计算

4.sparkContext生命周期
Hive on spark 会为每一个用户的会话,比如执行一次sql语句,创建一个sparkcontext,但是spark不允许在一个JVM内创建多个sparkcontext,因此,需要在单独额JVM中启动每一个会话的sparkcontext,然后通过RPC与远程JVM中的sparkContext进行通信。

5.本地和远程运行模式
Hive on spark 提供两种运行模式,本地和远程。如果将spark master设置为local






十二、Spark sql与spark core整合
案例:每日top3热点搜索词统计案例
日志格式:
日期、用户、搜索词、城市、平台、版本

需求:
1.筛选出符合条件(城市、平台、版本))的数据
2.统计出每天搜索的UV排名前三的搜索词
3.按照每天的top3搜索词的UV搜总次数,倒序排列
4.将数据保存到hive表中

实现思路分析
1.针对原始的数据(HDFS文件),获取输入RDD
2.使用filter算子,去针对输入RDD中的数据,进行数据过滤,过滤出符合条件的数据
2.1普通的做法:直接在filter算子函数中,使用外部的查询条件(map),但是这样的话,是不是查询条件map,会发送到每一个task上一份副本(性能并不好)
2.2优化后的做法:将查询条件,封装为Broadcast广播变量,在filter算子中使用Broadcast广播变量

3.将数据转换为“日期_搜索词,用户”的格式,然后对他进行分组,再次进行映射。对每天每个搜索词的搜索用户进行去重操作,并统计去重后的数量,即为每天每个搜索词的UV,最后获得(日期_搜索词,UV)

4.将得到的每天的搜索词的UV,RDD映射为元素类型的Row的RDD,将RDD转换为dataframe
5.将dataframe注册为临时表,使用spark sql的开窗函数,来统计每天的UV排名前三的搜索词,以及他的搜索UV,最后获知,是一个dateframe
6.将dateframe转换为RDD。继续操作,按照每天日期进行分组。并进行映射。计算出每天的top3所搜词的搜索uv的总数,然后将UV总数作为key,将每天的top3搜索词以及搜索次数,拼接为一个字符串
7.按照每天的top3搜索总UV,进行排序,倒序排序
8.将排好序的数据,再次映射回来,变成“日期_搜索词_UV”的格式
9.再次映射为dataframe,并将数据保存到hive表中

package com.spark.spark_sql;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.FlatMapFunction;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.broadcast.Broadcast;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RowFactory;
import org.apache.spark.sql.hive.HiveContext;
import org.apache.spark.sql.types.DataTypes;
import org.apache.spark.sql.types.DateType;
import org.apache.spark.sql.types.StructField;
import org.apache.spark.sql.types.StructType;

import scala.Tuple2;

/**
 * 每日top3热点搜索词统计
 * @author Administrator
 *
 */
public class DailyTop3Keyword {

    public static void main(String[] args) {
        SparkConf conf = new SparkConf().setAppName("DailyTop3Keyword");
        JavaSparkContext jsc = new JavaSparkContext(conf);
        HiveContext hiveContext = new HiveContext(jsc.sc());
        //针对hdfs上的文件中的日志,获取输入的RDD
        JavaRDD<String> rawRDD = jsc.textFile("hdfs://hadoop01:8020/keyword.txt");
        //伪造一份数据,查询条件
        //备注:实际上,在实际的企业的项目开发中,很可能,这个查询条件是J2EE平台发送到MySQL表中的
        //然后,这里实际上通常是会用Spring框架和ORM框架的,去提取MySQL表中的查询条件
        Map<String,List<String>> queryParamMap =new HashMap<String, List<String>>();
        queryParamMap.put("city", Arrays.asList("beijing"));
        queryParamMap.put("platform", Arrays.asList("android"));
        queryParamMap.put("version", Arrays.asList("1.0","1.2","1.5","2.0"));
        
        //将map封装为广播变量,每个work节点只拷贝一份数据即可,这样可以进行优化
        final Broadcast<Map<String, List<String>>> queryParamMapBroadcast = jsc.broadcast(queryParamMap);
        
        //使用广播变量进行筛选
        JavaRDD<String> filterRDD = rawRDD.filter(new Function<String, Boolean>() {
            private static final long serialVersionUID = 1L;

            @Override
            public Boolean call(String log) throws Exception {
                String[] logSplited = log.split("\t");
                String city =logSplited[3];
                String platform =logSplited[4];
                String version = logSplited[5];
                //与查询条件进行比较,任何一个
                Map<String, List<String>> queryParamMap = queryParamMapBroadcast.value();
                
                List<String> cities = queryParamMap.get("city");
                if(cities.size()>0 && !cities.contains(city)){
                    return false;
                }
                
                List<String> platforms = queryParamMap.get("city");
                if(platforms.size()>0 && !platforms.contains(platform)){
                    return false;
                }
                List<String> versions = queryParamMap.get("city");
                if(versions.size()>0 && !versions.contains(version)){
                    return false;
                }
                
                return true;
            } 
        });
        
        //将过滤出来的原始的日志进行映射为(日期_搜索词,用户)的格式
        JavaPairRDD<String, String> dateKeywordUserRDD = filterRDD.mapToPair(new PairFunction<String, String, String>() {

            
            private static final long serialVersionUID = 1L;

            @Override
            public Tuple2<String, String> call(String t) throws Exception {
                 String[] logSplited = t.split("\t");
                 String date =logSplited[0];
                 String user =logSplited[1];
                 String keyword =logSplited[2];
    
                return new Tuple2<String, String>(date+"_"+keyword, user);
            }
        });
        
        //进行分组,获取每天的搜索词,有哪些用户进行搜索了(没有去重)
        JavaPairRDD<String, Iterable<String>> dateKeywordUsersRDD = dateKeywordUserRDD.groupByKey();
        
        //对每天每个搜索词的搜索用户,执行去重操作,获得其UV值
        JavaPairRDD<String, Long> dateKeywordUVRDD = dateKeywordUsersRDD.mapToPair(new PairFunction<Tuple2<String,Iterable<String>>, String, Long>() {

            private static final long serialVersionUID = 1L;

            @Override
            public Tuple2<String, Long> call(Tuple2<String, Iterable<String>> t) throws Exception {
                String  dateKeyword = t._1;
                Iterator<String> users = t._2.iterator();
                //对用户进行去重,并且统计数量
                List<String> distinctUsers = new ArrayList<String>();
                
                while(users.hasNext()){
                    String user = users.next();
                    if(!distinctUsers.contains(user)){
                        distinctUsers.add(user);
                    }
                }
                //获取uv
                long uv = distinctUsers.size();
                return new Tuple2<String, Long>(dateKeyword, uv);
            }
        });
        
        //将每天每个搜索词的UV数据转换成dataframe
        JavaRDD<Row> dateKeywordUVRowRDD = dateKeywordUVRDD.map(new Function<Tuple2<String,Long>, Row>() {

            @Override
            public Row call(Tuple2<String, Long> v1) throws Exception {
                String date = v1._1.split("_")[0];
                String keyword = v1._1.split("_")[1];
                long uv = v1._2;
                return RowFactory.create(date,keyword,uv);
            }
        });
        
        List<StructField> structFields = Arrays.asList(
                DataTypes.createStructField("date", DataTypes.StringType, true),
                DataTypes.createStructField("keyword", DataTypes.StringType,true),
                DataTypes.createStructField("uv", DataTypes.LongType,true)
                );
        
        StructType structType =DataTypes.createStructType(structFields);
        DataFrame dateKeywordUVDF = hiveContext.createDataFrame(dateKeywordUVRowRDD, structType);
        
        //使用spark sql的开窗函数,统计每天的搜索排名前三的热点搜索词
        dateKeywordUVDF.registerTempTable("daily_keyword_uv");
        
        
        DataFrame dailyTop3KeywordDF = hiveContext.sql("select date,keyword,uv from "
                + "(select date,keyword,uv,row_number over (partition by date order by uv desc ) rank from daily_keyword_uv )tmp "
                + "where rank <=3");
        
        //将dateframe 转换为RDD,然后映射,计算出每天的top3搜索词的搜索uv的总数
        JavaPairRDD<String, String> Top3DateKeywordUvRDD = dailyTop3KeywordDF.javaRDD().mapToPair(new PairFunction<Row, String, String>() {

            @Override
            public Tuple2<String, String> call(Row row) throws Exception {
                String date = String.valueOf(row.get(0));
                String keyword =String.valueOf(row.get(1));
                Long uv = Long.valueOf(String.valueOf(row.get(2)));
                
                return new Tuple2<String, String>(date, keyword+"_"+uv);
            }
        });
        
        JavaPairRDD<String, Iterable<String>> Top3DateKeywordRDD = Top3DateKeywordUvRDD.groupByKey();
        JavaPairRDD<Long, String> uvDateKeywordsRDD = Top3DateKeywordRDD.mapToPair(new PairFunction<Tuple2<String,Iterable<String>>, Long, String>() {

            @Override
            public Tuple2<Long, String> call(Tuple2<String, Iterable<String>> t) throws Exception {
                String date = t._1;
                
                Iterator<String> keywordUvIterator = t._2.iterator();
                Long totalUv = 0L;
                String dateKeywordUv = date;
                while(keywordUvIterator.hasNext()){
                    String keywordUv= keywordUvIterator.next();
                    Long uv = Long.valueOf(keywordUv.split("_")[1]);
                    totalUv+=uv;
                    dateKeywordUv+=","+keywordUv;
                }
                return new Tuple2<Long, String>(totalUv, dateKeywordUv);
            }
        });
        //按照每天的总搜索进行倒序排序
        
        JavaPairRDD<Long, String> sortedUvDateKeywordsRDD = uvDateKeywordsRDD.sortByKey(false);
        
        //在此进行映射,将排序后的数据,映射回原始的格式Iterable<ROW>
        JavaRDD<Row> sortedRowRDD = sortedUvDateKeywordsRDD.flatMap(new FlatMapFunction<Tuple2<Long,String>, Row>() {

            @Override
            public Iterable<Row> call(Tuple2<Long, String> t) throws Exception {
                String dateKeywords=t._2;
                String[] dateKeywordsSplited = dateKeywords.split(",");
                String date = dateKeywordsSplited[0];
                List<Row> rows= new ArrayList<Row>();
                rows.add(RowFactory.create(date,
                        dateKeywordsSplited[1].split("_")[0],
                        Long.valueOf(dateKeywordsSplited[1].split("_")[1])
                        ));
                rows.add(RowFactory.create(date,
                        dateKeywordsSplited[1].split("_")[0],
                        Long.valueOf(dateKeywordsSplited[2].split("_")[1])
                        ));
                rows.add(RowFactory.create(date,
                        dateKeywordsSplited[1].split("_")[0],
                        Long.valueOf(dateKeywordsSplited[3].split("_")[1])
                        ));    
                return rows;
            }
        });
        
        //将最终的数据转换为dateframe
        DataFrame finalDF = hiveContext.createDataFrame(sortedRowRDD, structType);
        
        finalDF.saveAsTable("daily_top3_keyword_uv");
        jsc.close();
    }
}

 

转载于:https://www.cnblogs.com/yin-fei/p/10780003.html

你可能感兴趣的:(json,scala,java)