文章目录
回顾
线程安全问题的起因:静态类或公用的对象中成员变量进行更改。
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的压力。