写在开头:
本贴是博主学习自用,如有不对的地方,请各个大神指出。
如何在Linux上安装并配置Hive
##############
# HIVE 3.1.2 #
##############
# 1、解压并重命名
cd /opt/download
tar -zxvf apache-hive-3.1.2-bin.tar.gz -C /opt/software/
mv /opt/software/apache-hive-3.1.2-bin/ /opt/software/hive312
cd /opt/software/hive312
# 2、环境变量并激活
vim /etc/profile.d/my.sh
#-----------------------------------------
# hive
export HIVE_HOME=/opt/software/hive312
export PATH=$PATH:$HIVE_HOME/bin
#-----------------------------------------:
source /etc/profile
# 3、配置文件
mv conf/hive-default.xml.template conf/hive-default.xml
vim conf/hive-site.xml
#-----------------------------------------
<configuration>
<!--hdfs仓库路径-->
<property>
<name>hive.metastore.warehouse.dir</name>
<value>/hive312/warehouse</value>
</property>
<!--metastore(元)数据库类型-->
<property>
<name>hive.metastore.db.type</name>
<value>mysql</value>
<description>Expects one of [derby, oracle, mysql, mssql, postgres].</description>
</property>
<!--连接mysql字符串-->
<property>
<name>javax.jdo.option.ConnectionURL</name>mysql
<value>jdbc:mysql://192.168.71.128:3306/hive312?createDatabaseIfNotExist=true</value>
</property>
<!--mysql连接驱动-->
<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>com.mysql.jdbc.Driver</value>
</property>
<!--mysql连接账号-->
<property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>root</value>
</property>
<!--mysql本地连接密码-->
<property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>kb16</value>
</property>
<!--关闭schema验证-->
<property>
<name>hive.metastore.schema.verification</name>
<value>false</value>
</property>
<!--提示当前库名-->
<property>
<name>hive.cli.print.current.db</name>
<value>true</value>
<description>Whether to include the current database in the Hive prompt.</description>
</property>
<!--查询输出显示列名-->
<property>
<name>hive.cli.print.header</name>
<value>true</value>
<description>Whether to print the names of the columns in query output.</description>
</property>
</configuration>
#-----------------------------------------
${hive.exec.scratchdir} =DEFAULT=> /tmp/hive
is still used for other temporary files, such as job plans
#4、拷贝mysql驱动
cp /opt/download/mysql-connector-java-5.1.47.jar /opt/software/hive312/lib/
#5、更新guava包和hadoop一致
ls lib/|grep guava
# guava-19.0.jar
rm -f lib/guava-19.0.jar
find /opt/software/hadoop313/ -name guava*
#/opt/software/hadoop313/share/hadoop/common/lib/guava-27.0-jre.jar
#/opt/software/hadoop313/share/hadoop/hdfs/lib/guava-27.0-jre.jar
cp /opt/software/hadoop313/share/hadoop/hdfs/lib/guava-27.0-jre.jar lib/
#6、mysql授权
grant all on *.* to root@master01 identified by 'kb16';
flush privileges;
#7、初始化
bin目录下
schematool -dbType mysql -initSchema
s
#8、hive启动模式
#首先启动元数据服务
nohup hive --service metastore 1>/dev/null 2>&1 &
#1、方法一 hive客户端
hive
#2、方法二 基于metastore和hiveserver2的beeline
#启动hiveserver2服务
nohup hive --service hiveserver2 1>/dev/null 2>&1 &
beeline -u jdbc:hive2://localhost:10000
如何在hive中实现对手机号的加密
public class EncrypPhoneNumber extend UDF{
public String evaluate(String phoNum){
String encrypPhoneNum=null;
//判断手机号不为空,并且为11位
if(StringUtils.isNotEmpty(phoNum))&&phoNum.trim.length()==11{
//判断数据满足中国大陆手机号码规范
String regex ="^(1[3-9]\\d{9}$)";
Pattern p=Pattern.compile(regex);
Matcher m=p.matcher(phoNum);
if(m.matcher()){//进入这里的都是符合手机号规则的
//使用正则替换,返回加密后的数据
encrypPhoneNum=phoNum.trim().replaceAll("(\\d{3}\\d{4}(\\d{4})","$1****$2");
}else{
//不符合手机号规则,数据直接原封不动返回
encrypPhoneNum=phoNum;
}
}else{
//不符合11位,数据直接原封不动返回
encrypPhoneNum=phoNum;
}
return encrypPhoneNum;
}
}
//建一个maven工程,贴两个依赖
//粘到IDEA里打jar包,拖到moba里(位置任意)
//用虚拟机执行: add jar /root/hive-udf-1.0-SNAPSHOT.jar;(这个路径 要写对)
//注册成为临时函数,给UDF函数命名,格式为:create temporary function 函数名 as 'UDF类全路径';
//我改为create temporary encrypt_phoNum as '粘->类的copy reference';
//使用这个函数,如select encrypt_phoNum(); 括号里输你的参数
Part-1 基础入门
第一章
第一节 简单介绍
第二节 风雨十年
第三节 Spark和Hadoop框架的对比
首先他们两都是分布式应用框架。
类型:Hadoop框架不仅有计算框架(MapReduce),还有存储和调度;而Spark仅做为一个计算框架。
场景:Hadoop主要应用于海量批数据处理,计算中间结果由磁盘迭代(Map先把计算结果写到HDFS硬盘之上,再有reduce算子把硬盘上的数据读取出来);Spark结果基于内存迭代,适用于批流一体数据的处理。
编程范式:Hadoop计算框架是mapReduce计算框架,该框架只有map和reduce两种算子,可实现的功能较单一,若要实现较复杂功能则需多次map或reduce算子转换;Spark算子非常多,实现相同功能比MapReduce用的算子更少。
运行:Hadoop中的Task任务以进程的方式进行,启动较慢,消耗资源较多;Spark是线程,线程之间快切,资源消耗较少。
第四节 四大特点
1、速度快
2、使用简单
算子的用法都非常简单
3、能力范围广,适用语言多
SparkCore是Spark的核心,可以对非结构的数据处理,同时支持多种编程语言API进行开发。
sparkSql可以进行对结构化文件的处理
Streaming可以对流式文件的处理
MLib可以对进行机器学习任务的处理
GraphX可以进行图计算
4、兼容多种数据源和多种运行模式
运行在云上(即容器中)。
数据源:可以从多种途径读写数据。
速度快:比MapReduce快10倍 使用简单:Python,Java,SQL都能用 范围广:SparkSQL(结构化文件),SparkStreaming(流文件),MLib(机器学习),Graphx(图计算) 兼容好: 1. 支持多种运行方式:Hadoop,Mesos,Standlone,K8s 2. 支持多种数据源:文件系统(HDFS,text,ORC,Json,csv,parquet)。数据库(Oracle,MySQL)。Nosql(HBase,Redis,ES)。消息对象(Kafka)
第五节 框架模块
第六节 运行 模式
-
StandAlone模式就是它的Master和Worker分布在多个Linux集群环境上
-
YARN模式就是Master和Worker分布在多个Yarn的容器内部,这些容器组成了集群环境
第七节 架构角色
Yarn角色回顾:
Spark角色:
和Yarn的角色基本功能基本一样,只不过叫法不一样。
特殊情况:只有在local模式下,Driver可以既管理,又干活。
第八节 服务器环境
第二章
Local模式原理
Local下的角色分布:
Linux上安装Anaconda
Spark Local模式部署
-
pyspark程序启动
cd /export/server/spark/bin # 启动pyspark程序 ./pyspark # 执行此命令前需先启动yarn,hadoop。# start-all.sh
# 开启下面spark环境后,Python代码和spark代码均可以执行
第三章 StandAlone
第一节 StandAlone运行原理
第二节 部署
第三节 测试
第四节 Spark程序运行层次划分
第四章 StandAlone HA运行原理和部署
StandAlone 的HA模式
为什么会有HA模式:因为单节点的Master会存在单点故障的问题,一旦Master宕机,整个Spark集群会陷入瘫痪。
-
解决方案:
部署测试
第五章 Stand On Yarn(重点)
第一节 Stand On Yarn 运行原理和部署
第二节 部署和测试
第三节 两种部署模式的区别
分别是集群模式和客户端模式。
集群模式图解:
客户端模式图解:
第四节 两种部署模式的演示和总结
第五节 两种模式任务提交流程
客户端模式的提交流程:
集群模式的提交流程:
第六章 框架,类库
框架和类库
类库:其实就是一堆别人写好得代码,可以导入进行使用,Numpy,Pandas就是Python的类库,但它自己不能单独运行,无法自己创建一个进程。Pandas就是一个类库,主要用于小规模数据梳理。
框架:可以独立运行,比如Spark,Hive,Hadoop,JavaScript都是框架,是提供编程结构的一种软件产品。Spark是一个独立的框架,主要用于大规模数据的处理。
PySpark类库和bin/pyspark的区别:
一个是类库,有Spark官方提供;一个是客户端应用程序,用于写SparkApi。
PySpark类库介绍
注意使用场景:以PySpark编写的程序,可以提交到Spark框架中去运行。
PySpark安装
-
1.先进行Anaconda的安装:
-
再进行PySpark安装
如果是在pycharm中,直接pip就行,如果是安装在Linux虚拟机上,需要先切换到root,再激活pyspark,最后执行安装命令。
第八章
第一节 Spark运行角色回顾
第二节 分布式代码执行分析
第三节 Python On Spark执行原理
简单图例版:
Part-2 SparkCore
第一章 rdd介绍
什么是RDD
为什么需要RDD
首先,分布式计算需要:分区控制,shuffle控制,数据存储/序列化/发送,数据计算API等等。
高容错的,可以在内存中,实现集群化计算的,弹性分布式数据集。
RDD五大特性-
前3大特性都是每个RDD具备的,后两个是可选的。
特性1-RDD是有分区的(本地集合无)
RDD是个抽象对象,他的物理实体是分区,每个物理的分区里存量物理的数据。每个分区的数据组合起来,就构成了抽象的RDD对象。
特性2-每个算子都会作用于RDD的每个分区上
特性3-RDD之间是有血缘(依赖)的
后一个RDD总是依赖于前一个RDD
特性4-kv型RDD可以有分区器
kv型RDD:RDD内存储的是二元元祖
特性5-RDD的分区规划会尽量靠近数据所在的服务器
WordCount结合RDD特性进行执行分析
rdd的创建(两种方式)
rdd的创建分为两步,第一步先要构造rdd入口对象sc。第二步通过sc入口对象构造rdd,构造rdd有两种方式:其一是并行化本地集合;其二是读取外部文件;其三还有个读取小文件专用方法wholeTextFile
当在parallelize()中不写分区数的时候,默认分区数是根据CPU的核心数来定。
textFile的第二个参数(最小分区数)也可以省略,不写就按系统默认的分区数来。
默认分区数主要跟文件大小有关。如果是读取hdfs,主要跟它的block块有关。
RDD的创建方式三:wholeTextFile
第一个参数也可以读文件夹名称,只不过它通过collect()方法打印出来的是一个个的元祖,元祖里面是kv,k是文件路径,v是文件内容。
要把它里面的文件提取出来可以通过map方法
第二章 算子
算子概念和分类
算子概念:
(Rdd是弹性分布式数据集合)算子其实就是各种封装好的方法,里面有计算逻辑;与方法不同的是,算子是作用于分布式集合(即分布于Rdd)上的,方法是作用于作用本地集合(list)上的,可以理解为方法是个体伤害,算子是群里伤害。
方法你可以理解为战士,数据(不管是本地集合还是分布式Rdd),你可以理解为敌人(攻击对象),方法是这节对数据进行操作,一刀一刀砍(直接伤害)。而算子可以理解为法器,它其实本身不能直接对数据造成伤害,它的参数是传入一个战士(方法),通过这个战士通过算子对数据rdd造成伤害(间接),与战士不同的是,多了这样一个间接嵌套,这个法师把对敌人的单个伤害变成了群体伤害。群体伤害是算子统一被动。
举个例子,filter这个算子,主动技能是对敌人进行过滤,但是它不能直接过滤,它的参数是接收一个满足过滤条件的语句,被动是实现了群体多分区(副本)过滤,大大提高了刷本效率。
算子分类
转换算子和行动算子。
转换算子 顾名思义,即对数据(rdd)进行操作,是原rdd编程另一种rdd。需注意,转换算子是懒加载,即若没有行动算子配合,转换算子写再多也是执行的。
行动算子 :相当于比赛中发令枪的作用,前面的转换算子都是在构建对数据的执行计划,一步一步调用什么算子把什么数据变成什么样都是转换算子说了算,最后的执行算子才是决定这个计划是否被执行的关键。
算子如何区分:转换字段对数据操作后,返回的依旧是个rdd。行动算子操作后,返回的不是rdd。
collect算子
【收集法师】行动算子,用于将分布在集群中的数据收集到内存中,它以数组的形式返回。
注意:调用改算子后,所有分布在各个节点上的数据,都会被拉取到单个节点的内存中,所以处理特别大规模的数据时谨慎使用。
应用该算子的场景:就是需要以本地的方式处理所有数据时,比如数据本地落盘时。如果数据量较大,可以考虑先对数据进行聚合,分区或取样等操作,以减少对单节点内存的占用量。
map算子
【法师之神--转换法器】这个算子的作用非常强大(),他可以再和任何一个(即只允许接受一个参数)法器(方法或函数)配合打出非常多的效果,将战士砍杀后的结果返回一个新的rdd。但需注意:传入的敌人和他们处理返回后的敌人,类型必须相同。
from pyspark import Sparkconf, SparkContext if __name__ == '__main__': conf = SparkConf().setAppName('test').setMaster('local[*]') sc = SparkContext(conf=conf) rdd = sc.parallelize([1,2,3,4,5,6],3) # 方式一:定义方法,作为算子,传入函数体 def add(data): return data * 10 print(rdd.map(add).collect()) # 方式二:直接匿名函数 print(rdd.map(lambda x:x * 10).collect())
mapPartitions算子
和map法师处理的结果是一样的,只是过程有区别,map是一条一条处理数据,mapPartitions是一个分区一个分区处理数据。
它和map的区别在于,map是的网络传输是一次传输分区内一条数据,它是一次性传输一整个分区的数据。在cpu执行层面,没有省下来任何东西,但在空间利用率(IO)上,有很大提升,综上,mapPartitino的性能,比map好很多。
foreach算子
【法师之神mini--无返回值转换法师】
功能:对rdd的每个元素,执行你提供的逻辑操作(和map一个意思),但该算子没有返回值。可以认为它一个没有返回值的map方法。当你需要进行一些不需要返回值的操作时,可以使用该方法(因为他不跟driver汇报,直接在Executor上输出,效率会高一些)。
foreachPartition算子
【行动算子-foreach加强法师】
和foreach的作用一样,只不过foreach是一条一条处理数据,foreachePartition是一次处理一个分区的数据。
#coding:utf-8 from pyspark.sql import SparkSession def map_func(data): result=[] for i in data: result.append(i*10) print(result) if __name__ == '__main__': spark=SparkSession.builder.appName('test').master('local[*]').getOrCreate() sc=spark.sparkContext rdd= sc.parallelize([1,2,3,4,5,6,7,8],3) print(rdd.foreachPartition(map_func)) # 这里其实不用print了,因为foreachPartition没有返回值
flatMap算子
【法师之母--扁平法师】,她和map的作用有点类似,也是只和一个战士配合,返回一个处理后的rdd;只不过她比map多做一步的是,如果返回的这个rdd是二维的,它会把这个二维自动变成一维。即list嵌套list就是二维,她会把嵌套拆掉,变成只有一个list。
glom算子
【法目之敌--嵌套法师】
和flatMap的作用相反
rdd.glom().flatMap(lambda x:x).collect() # 加嵌套,解嵌套,等于没变
mapValues算子
注意这个算子的限定条件,只针对二元元祖的rdd,而且这个二元元祖是里面是(k, v)结构。
假设一个个的二元元祖是刘禅、元歌类型的敌人,(k即本体,v即傀儡),这个法师的作用就是,当遇到一个个这种类型的敌人的时候,不管你来的是元歌还是刘禅,我统统忽视你的k,而只针对你的傀儡进行伤害,返回的依旧是一个列表里嵌套的二元元祖。
rdd = sc.parallelize([('a',1), ('a',1), ('b',1), ('c',3)]) rdd1 = rdd.mapValues(lambda value: value*10).collect().print() >>> [('a',10), ('a',10), ('b',10), ('c',30)]
groupBy算子
【分组法师】
他的作用是对数据按照你指定的那个列进行分组,并返回一个重分组后的rdd,而且他对传入前和传入后rdd的类型不做限制,就是你的传参可以是数值类型,返回类型可以是字符串。
分组的那个列就是你lambda返回的后的值。
# coding:utf-8 from pyspark.sql import SparkSession if __name__ == '__main__': spark = SparkSession.builder.appName('test').master('local[*]').getOrCreate() sc = spark.sparkContext rdd=sc.parallelize([('a',1),('b',1),('b',1),('c',1)]) rdd1=rdd.groupBy(lambda x: x[0]) print(rdd1.collect()) # [('b', <pyspark.resultiterable.ResultIterable object at 0x000001C7C2D657F0>), ('c', <pyspark.resultiterable.ResultIterable object at 0x000001C7C2D65850>), ('a', <pyspark.resultiterable.ResultIterable object at 0x000001C7C2D65940>)] # 此时元祖的第二个元祖是一个Iterable的迭代对象,需要强转成list rdd2=rdd1.map(lambda x:(x[0],list(x[1]))) print(rdd2.collect()) # [('b', [('b', 1), ('b', 1)]), ('c', [('c', 1)]), ('a', [('a', 1)])] spark.stop()
groupByKey算子
【分组法师--按key分组法师】
功能:针对KV型的rdd,但它和groupBy算子不同的是,这个法师会自动按照Rdd的key分组,不需要你指定。
用法:rdd.groupByKey() 里面不需要有传入的参数
注意:和reduceByKey不同的是,reduce是分组聚合,而groupByKey只是单纯分组
reduceByKey算子
【分组法师--按key分组聚合法师】
注意:reduceByKey中接收的函数,只负责聚合,不理会分组。这个算子会自动按照key分组。
面试题-groupByKey和reduceByKey的区别
它两本质的区别在于,groupByKey先shuffle,再聚合;reduceByKey是先组内聚合,再shuffle,大大减少了shuffle量,所以性能高。
reduceByKey在分区内会先做聚合,这样就能减少网络IO的次数
如果业务要用的逻辑是:分组+聚合,想都不用想,直接用reduceByKey
filter算子
【过滤法师】
功能:返回的是一个布尔型,把为True的数据进行保留,为False的丢弃,返回一个新的rdd
from pyspark import SparkContext, SparkConf from pyspark.sql import SparkSession conf = SparkConf().setAppName("filter_demo").setMaster("local") sc = SparkContext(conf=conf) spark = SparkSession(sc) people_rdd = sc.parallelize([("Alice", 25), ("Bob", 30), ("Charlie", 35)]) # 可以直接对rdd进行过滤算子操作 people_rdd.filter(lambda x: x[1]>=30) # 也可以先转成DataFrame,在使用该算子进行过滤 people_df = spark.createDataFrame(people_rdd, ["name", "age"]) filtered_df = people_df.filter(people_df.age >= 30) rdd1 = sc.parallelize([1, 2, 3, 4, 5, 6]) filtered_rdd = rdd1.filter(lambda x: x >= 4) # 满足 x>=4 的值保留,不满足的丢弃 rdd2 = sc.parallelize(["apple", "banana", "orange", "peach"]) filtered_rdd = rdd2.filter(lambda x: "a" in x)
distinct算子
【去重法师】
对相同rdd的数据进行去重,返回一个新的rdd。里面参数是分区数量,一般不填。
# coding:utf-8 from pyspark.sql import SparkSession if __name__ == '__main__': spark = SparkSession.builder.appName('test').master('local[*]').getOrCreate() sc = spark.sparkContext rdd = sc.parallelize([1, 2, 1, 23, 4, 23, 3, 5, 3, 1, 5]) rdd1 = rdd.distinct() print(rdd1.collect()) >>> [1, 2, 3, 4, 5, 23]
union算子
【合并法师】
将两个rdd合并是一个rdd返回一个新的rdd。rdd的类型不同也是可以合并的。
join算子
【连接法师】
功能:对两个rdd执行join操作(可实现SQL的内、外连接)
注意:join算子只能用于kv型(二元元祖)的Rdd,关联条件就是rdd1的key去join(rdd2的key)
语法:
rdd.join(other_rdd) # 内连接 rdd.leftOuterJoin(other_rdd) # 左外 rdd.rightOuterJoin(other_rdd) # 右外
注意:join算子必然产生shuffle,导致性能降低。
intersection算子
【取交集法师】
# coding:utf-8 from pyspark.sql import SparkSession if __name__ == '__main__': spark = SparkSession.builder.appName('test').master('local[*]').getOrCreate() sc = spark.sparkContext rdd1 = sc.parallelize([('a', 1), ('b', 1)]) rdd2 = sc.parallelize([('a', 1), ('c', 1)]) res=rdd1.intersection(rdd2).collect() print(res) >>> [('a', 1)]
sortBy算子
【排序法师】
sortByKey算子
【按key排序法师】
案例-提交到Yarn执行
countByKey算子
【统计key法师】行动算子,返回的不是rdd。
# coding:utf-8 from pyspark.sql import SparkSession if __name__ == '__main__': spark = SparkSession.builder.appName('test').master('local[*]').getOrCreate() sc = spark.sparkContext rdd = sc.textFile('../Data/Input/words.txt') result = rdd.flatMap(lambda x: x.split(' ')).map(lambda x: (x, 1)).countByKey() print(result) >>> defaultdict(<class 'int'>, {'hadoop': 3, 'flink': 1, 'spark': 3, 'java': 2, 'sql': 1})
reduce算子
fold算子
takeSample算子
【抽样法师】行动算子,最外层返回的是一个列表。
随机从现有rdd中抽取你指定条数的数据
print(rdd1.takeSample(True, 3)) >>>> [('SCOTT', 1), ('FORD', 1), ('TURNER', 1)]
takeOrdered算子
【排序法师】
第一个参数是要几个数据,第二个参数是对排序的数据修改,但不会改变数据本身
#coding:utf-8 from pyspark.sql import SparkSession if __name__ == '__main__': spark=SparkSession.builder.appName('test').master('local[*]').getOrCreate() sc=spark.sparkContext rdd=sc.parallelize([1,3,2,4,7,9,6],1) print(rdd.takeOrdered(3)) print(rdd.takeOrdered(3,lambda x:-x)) # 相当于倒序排列 >>>> [1, 2, 3] [9, 7, 6]
saveAsTextFile算子
【落盘法师】行动算子
功能:将rdd的数据写入文本文件中,支持本地写出,hdfs等文件系统。注意,这个算子写出文件的时候,rdd有几个分区,它就写出成几个文件。
partitionBy算子
【指定分区内容法师】
#coding:utf-8 from pyspark.sql import SparkSession def process(k): if 'hadoop'==k or 'hello'==k:return 1 if 'scala'==k or 'spark'==k:return 2 return 3 if __name__ == '__main__': spark=SparkSession.builder.master('test').master('local[*]').getOrCreate() sc=spark.sparkContext rdd = sc.parallelize([('hadoop',1),('hadoop',1),('spark',1),('spark',1),('hello',1),('flink',1),('scala',1),('java',2)]) print(rdd.partitionBy(3, process).glom().collect()) >>>>>>> [[('flink', 1), ('java', 2)], [('hadoop', 1), ('hadoop', 1), ('hello', 1)], [('spark', 1), ('spark', 1), ('scala', 1)]]
repartition、coalesce算子
【修改分区数量法师】
这两个算子尽量少用!!!就算真要用,也尽量减少分区(比如为了做排序,设置为1),而不是增加分区。 因为它会极大可能导致内存迭代管道增加,从而导致shuffle。这两个api本质上是一回事,但建议用coalesce,因为它的第二个参数是个安全阀,当你把shuffle=True时,才会真正执行你要增加的分区数。
first算子
【行动算子--取首级法师】行动算子
取出rdd的第一个元素,元素类型本身是啥,取出来就是啥。
take算子
【取前N元素法师】行动算子
取出rdd中前N个元素,返回一个list给你
top算子
【降序取前N法师】行动算子
对rdd的数据按降序排序,取最大的前N个元素
count算子
【计数法师】行动算子
计算rdd有多少条数据,返回一个数字给你。
第三章 rdd缓存
RDD的数据是过程数据
RDD的缓存
用法:很简单,只需要调用cache()这个api,当前这个rdd就被保存起来了(内存)。
建议:一般把从文件读出来的RDD就可以做一步持久化的操作了。
RDD的CheckPoint
总结
第四章 rdd案例
jieba库简单使用
jieba库是一个十分优秀的中文分词第三方库(需要手动下载),它的分词分为3种模式:
-
精确模式:jieba.cut():把文本精确的分开,不存在冗余的单词
-
全模式:jieba.cut(s, cut_all=True):把文本中所有可能的词语都扫描出来,有冗余
-
搜索引擎模式:jieba.cut_for_search(s):在精确模式的基础上,对长词再次切分,有冗余
generator
generator是一个可迭代对象,可以通过list()包裹,将其强转为一个列表,也可以通过for循环,将其里面的元素进行遍历。
# -*- coding:utf-8 -*- import jieba if __name__ == '__main__': content='小明毕业于中国科学院计算研究所,后在清华大学深造' result = jieba.cut(content,cut_all=True) result1= jieba.cut(content) result2= jieba.cut_for_search(content) print(list(result)) print(list(result1)) print(list(result2)) >>> ['小', '明', '毕业', '于', '中国', '中国科学院', '科学', '科学院', '学院', '计算', '研究', '研究所', ',', '后', '在', '清华', '清华大学', '华大', '大学', '深造'] ['小明', '毕业', '于', '中国科学院', '计算', '研究所', ',', '后', '在', '清华大学', '深造'] ['小明', '毕业', '于', '中国', '科学', '学院', '科学院', '中国科学院', '计算', '研究', '研究所', ',', '后', '在', '清华', '华大', '大学', '清华大学', '深造']
需求1开发
将高频词汇进行统计,打印前5名
\t 的意思是 :水平制表符。将当前位置移到下一个tab位置。 \r 的意思是: 回车。将当前位置移到本行的开头。 \n 的意思是:回车换行。将当前位置移到下一行的开头。 \f 的意思是:换页。将当前位置移到下一页的开头
# coding:utf-8 from pyspark.sql import SparkSession from pyspark.storagelevel import StorageLevel import jieba def context_jieba(data): res = jieba.cut_for_search(data) l=[] for i in res: l.append(i) return l def filter_words(data): return data not in ['帮','谷','客'] def convert_words(data): if data == '传智播': data='传智播客' if data == '院校': data='院校帮' if data == '博学': data='博学谷' return (data,1) if __name__ == '__main__': spark = SparkSession.builder.appName('test').master('local[*]').getOrCreate() sc = spark.sparkContext file_rdd = sc.textFile('../../Data/Input/SogouQ.txt') split_rdd = file_rdd.map(lambda x: x.split('\t')) split_rdd.persist(StorageLevel.MEMORY_ONLY) print(split_rdd.takeSample(True, 3)) # 从这开始才是需求1的开发,前面都是公共代码 content_rdd = split_rdd.map(lambda x: x[2]) words_rdd = content_rdd.flatMap(context_jieba) filter_rdd = words_rdd.filter(filter_words) result_rdd = filter_rdd.map(convert_words) print(result_rdd.reduceByKey(lambda a, b: a + b).sortBy(lambda x: x[1], ascending=False).take(5))
需求2开发
需求3开发
代码:
# -*- coding:utf-8 -*- import jieba def context_jieba(data): seg = jieba.cut_for_search(data) l = list() for word in seg: l.append(word) return l def filter_words(data): return data not in ('谷', '帮', '客') def append_words(data): if data == '传智播': data = '传智播客' if data == '院校': data = '院校帮' if data == '博学': data = '博学谷' return (data, 1) def extract_user_and_word(data): user_id = data[0] content = data[1] words = context_jieba(content) return_list = list() for word in words: if filter_words(word): return_list.append(user_id + '_' + append_words(word)[0], 1) return return_list
# -*- coding:utf-8 -*- from numpy import add from pyspark import SparkContext, SparkConf, StorageLevel from defs import context_jieba, filter_words, append_words, extract_user_and_word if __name__ == '__main__': conf = SparkConf().setAppName('need_1').setMaster('local[*]') sc = SparkContext(conf=conf) file_rdd = sc.textFile('SogouQ.txt') split_rdd = file_rdd.map(lambda x: x.split('\t')) print(type(split_rdd)) print(split_rdd.collect()) # 因为要对split_rdd多次使用 split_rdd.persist(StorageLevel.MEMORY_AND_DISK) # TODO 需求1:对用户搜索的关键词进行分析 context_rdd = split_rdd.map(lambda x: x[2]) words_rdd = context_rdd.flatMap(context_jieba) filter_rdd = words_rdd.filter(filter_words) append_rdd = filter_rdd.map(append_words) result1 = append_rdd.reduceByKey(lambda a, b: a + b). \ sortBy(lambda x: x[1], ascending=False, numPartitions=1). \ take(5) print('需求1的结果是:', result1) # TODO 需求2:用户和关键词组分析 # 1,我喜欢传智播客 # 1+我 1+喜欢 1+传智播客 user_content_rdd = split_rdd.map(lambda x: (x[1], x[2])) user_content_with_one_rdd = user_content_rdd.flatMap(extract_user_and_word) # 对内容进行分组,聚合,求前5 result2 = user_content_with_one_rdd.reduceByKey(lambda a, b: a + b). \ sortBy(lambda x: x[1], ascending=False, numPartitions=1).take(5) print('需求2结果:', result2) # TODO 需求3:热门搜索时段分析 time_rdd = split_rdd.map(lambda x: x[0]) # 对时间进行处理,只保留小时进度即可 hour_with_one_rdd = time_rdd.map(lambda x: (x.split(':')[0], 1)) # 分组,聚合,排序 result3 = hour_with_one_rdd.reduceByKey(add). \ sortBy(lambda x: x[1], ascending=False, numPartitions=1). \ collect() print('需求3结果:', result3) split_rdd.unpersist()
提交到Yarn集群运行
把上述代码修改的地方:
-
删掉 conf = SparkConf().setAppName('need_1').setMaster('local[*]')中的.setMaster(...),因为在命令行提交模式下,需要指定master为yarn模式,如下图:
-
将本地文件路径改成hdfs上的路径
file_rdd = sc.textFile('SogouQ.txt') --> file_rdd = sc.textFile('hdfs://node1:8020/Input/SogouQ.txt')。因为集群模式下,无法读到本地的文件路径。
-
删掉setMaster,因为提交到集群的时候可以通过参数来指定。
-
把sc.textFile读取文件的位置改成hdfs对应的位置
-
给所有Linux节点安装jieba库
普通提交:
总结
第五章 共享变量
广播变量
应用场景:
本地集合和分布式对象(rdd)进行关联时,且本地集合数据量不是很大的情况下,可以将本地集合对象封装为广播变量。 这样可节省: 1、网络IO的次数 2、Executor的内存占用 备注:本地集合跑的是driver,rdd跑的是Executor 只要封装成广播变量以后,driver的本地集合就会给分布式的Executor的每个进程都同步一份,而每个Executor进程的资源又都是共享的,在Executor里可能会有多个分区,每个分区是一个线程,线程就可以共享driver同步过来的这个集合。如果不是广播变量,那driver就需要给每个分区同步一次,增加了网络IO,造成内存资源的浪费。
解决方案:
用法:
1.将本地集合listA,用sc.broadcast(listA)套一下,返回的是一个广播变量broadCastA(本质是一个集合) 2.用这个广播变量的时候,只需要broadCast.value方法,即可取出这个集合。
# -*- coding:utf-8 -*- from pyspark import SparkContext, SparkConf def map_func(data): id = data[0] name = '' for stu_info in broadcast.value: stu_id = stu_info[0] # stu_id是广播变量 if id == stu_id: # ID是rdd name = stu_info[1] return (name, data[1], data[2]) if __name__ == '__main__': conf = SparkConf().setAppName('broadcast').setMaster('local[*]') sc = SparkContext(conf=conf) stu_info_list = [(1, '张大仙', 11), (2, '王晓晓', 13), (3, '张甜甜', 11), (4, '王大力', 11)] # 将本地集合转为广播变量 broadcast = sc.broadcast(stu_info_list) score_info_rdd = sc.parallelize( [(1, '语文', 99), (2, '数学', 99), (3, '英语', 99), (4, '编程', 99), (1, '语文', 99), (2, '编程', 99), (3, '语文', 99), (4, '英语', 99), (1, '语文', 99), (3, '英语', 99), (2, '编程', 99) ]) print(score_info_rdd.map(map_func).collect()) >>> [('张大仙', '语文', 99), ('王晓晓', '数学', 99), ('张甜甜', '英语', 99), ('王大力', '编程', 99), ('张大仙', '语文', 99), ('王晓晓', '编程', 99), ('张甜甜', '语文', 99), ('王大力', '英语', 99), ('张大仙', '语文', 99), ('张甜甜', '英语', 99), ('王晓晓', '编程', 99)]
适用场景:本地集合对象/rdd本身很小 和另一个RDD进行关联的时候,可以将本地集合对象或这个小的RDD分装成广播变量。
因为rdd和rdd关联是用的join算子,比产生shuffle,倒不如把这个小的rdd直接全部分到这个大的RDD的分区上,这样能省去大量网络IO
累加器
用法:acmlt = sc.accumulator(0) 0表示初始变量设为0
使用了累加器后的代码演示:最终的结果为10
from pyspark import SparkContext, SparkConf if __name__ == '__main__': conf = SparkConf().setAppName('accumulator').setMaster('local[*]') sc = SparkContext(conf=conf) rdd = sc.parallelize([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 2) acmlt = sc.accumulator(0) def map_func(data): global acmlt acmlt += 1 print(acmlt) rdd.map(map_func).collect() print(acmlt)
这段代码说明,当生成rdd3的时候,上面这个rdd2已经collect过,失效了(因为rdd是过程数据),此时rdd2需要重新溯源,继续调用一遍map_func方法,而此时的map_func里acmlt已经累加过一遍变成10了,所以会在10的基础上继续累加,所以rdd3的结果为20。
上述问题的解决办法:
加个cache()
总结:在生产环境中,用到累加器的时候,一定要注意上述问题,会不会出现重复累加的现象。
广播变量累加器综合案例
总结
第六章 DAG
本章内容是重点理解内容,也是面试常问内容,体现了对Spark底层的理解
DAG
DAG:有向无环图,有方向,无闭环。说白了,这个DAG就是Spark给RDD做的一个工作计划,告诉RDD接下来每一步要做什么。
通过查看Spark的4040端口,可以看到Spark每个任务的DAG,效果如下所示:
Job和Action的关系
一个Spark任务在运行的时候,会分成好几个Job。Job就是子任务。
Action是执行链条的开关(RDD迭代链条的开关)。上图中一个Action会产生一个Job,一个Action对应一个Job,每个Job都有各自的DAG图。
宽窄依赖和阶段划分
窄依赖
不管子分区接收多少个父分区,只要父分区的数据全部只给一个子分区,就叫窄依赖。
宽依赖(shuffle)
阶段划分
内存迭代计算
不要轻易改分区计算管道,即不要随意使用重分区算子,因为重分区就以为是shuffle,增加一次shuffle导致内存计算管道变短,性能下降一次。
上图总结:
横向上:一个task可以贯穿很多个RDD进行处理,但只能RDD中的一个分区。纵向上:一个RDD有几个分区,就会被几个task进行处理。
简而言之就是,我算子多,窄依赖之间依靠内存迭代。就这两点速度就很快。
Spark并行度
注意:并行度不是设定RDD分区,而是先设定task的数量,然后才根据并行度的需要,所以RDD被构造和task一样的分区数。总结:先有3个并行度,才有3个分区。一个分区只会被一个task去处理。一个task是可以处理多个RDD的。但是一个task只能处理一个RDD的一个分区的数据。
全局并行度
Spark任务调度
总结:
task1和task4,他两走的是网络(本地回环网络)。原因:虽然他两都在一台机器上,但是他两是在两个Executor上,而每个Executor是一个进程,进程和进程之间通讯是无法直接通过内存直接交流的。如果task1和task4是在两台不同的服务器上,那它两走的是交换机网络。
综上,一个服务器,开一个Executor就够了,开多了,他们还是走的网络交互,而不是内存。如果集群有100台服务器,那设置Executor参数的时候,设定为100就够了。
如果服务器1有16个CPU,那只要task有16个,那每个CPU都能用得上。
Task调度器
它主要监控任务的执行情况,定时做汇报的。相当于监工,DAG调度器相当于管家。
Spark运行概念名词解释和层级梳理
总结
Part-3 SparkSql
第一章
-
目标
了解SparkSql框架的基础概念和发展历史
掌握SparkSQL DataFrame Api开发
掌握SparkSQL运行流程
掌握SparkSQL和hive集成
spark快速入门
-
什么是SparkSQL
SparkSql是Spark用于处理结构化数据的一个Spark的模块。SparkSQL只适用于处理结构化数据,而RDD可以处理非结构化数据
半结构化数据:日志文件 结构化数据:数据库数据 半结构化数据和结构化数据的区别就是有无 元数据 结构化数据里的表头,就是元数据
Spark应用对堆内存的计算效率比MapReduce对堆内存的计算效率快很多。MapReduce内存完全依赖于Java的垃圾回收机制。就是即使你给MapReduce大的内存,他也有可能没法充分利用。
sparkSQL概述
-
为什么学
-
SparkSQL本身十分优秀,支持SQL,性能强,可自动优化,API简单,兼容hive
-
企业大面积使用其处理业务数据(离线开发,数仓搭建,科学计算,数据分析)
-
特点
-
融合性:可以无缝融合在代码中,随时使用SQL处理数据
-
统一数据访问:一套API可以读写不同数据源(csv,json,txt等)
-
hive兼容:可以使用sparkSQL直接计算并生成hive数据表
-
标准化连接:可以通过jdbc或odbc连接各种各样的数据库进行数据交互
-
发展历史 - 前身shark框架
2014年诞生
2016年成熟
2019年发布3.0版本
第二章
sparkSQL与hive异同和SparkSQL的数据抽象
-
SparkSQL发展
DataFrame数据抽象
SparkSession执行入口环境
构造出SparkSession这个环境入口对象以后,可以进行SparkSQL编程,也可以进行Rdd编程,后面统一用这个,不用SparkContext对象了
# coding:utf8 from pyspark.sql import SparkSession if __name__ == '__main__': spark = SparkSession. \ builder.appName('create'). \ master('local[*]'). \ config('spark.sql.shuffle.partitions', 1). \ getOrCreate() sc = spark.sparkContext df = spark.read.csv('../Data/Input/scores.csv', sep=',',header=False) df2=df.toDF('id','subject','score') df2.printSchema() df2.createTempView('scores') spark.sql(''' select * from scores where subject='math' ''').show() df2.show()
第三章 创建DataFrame
创建DataFrame的schema
DataFrame其实就是个二位表结构的数据,而Schema就是二维表结构的信息。这节说的就是我们创建这个表时要先指定这个表结构中的信息,即创建schema。
创建schema有两种方法:不管那种都要使用StructType()这个API
法一:直接通过add方法,一列一列添加
df = StructType(). \ add('colName1',StringType(),nullable=False). \ add('colName2',IntegerType(),nullable=True). \ ...
法二:通过StructField
df = StructType([ StructField('colName1', StringType(), True), StructField('colName2', DoubleType(), False), StructField('colName3', IntegerType(), False), ... ])
利用Rdd的toDF()构建DataFrame
toDF()这个API的功能是,把rdd转成DataFrame。他有两种写法
-
一种是传入一个完成的schema,schema中含有列名,列类型和列是否可以为空;
rdd.toDF(['colName1', 'colName2', 'colName3'])
-
一种是只传列名,列类型依据自动推断。
schema = StructType().... rdd.toDF(schema)
构建的DataFrame怎么再转成RDD
只需要将dataFrame.rdd即可转成rdd,后面再可以跟rdd的算子了。
用pandas的DataFrame构建DataFrame
依靠spark.createDataFrame(PDF),其返回值是Spakr的DataFrame,其中PDF为pandas的DataFrame。
读取text文件创建DataFrame
读取json文件创建DataFrame
读取csv文件创建DataFrame
读取parquet文件创建DataFrame
从HDFS读文件创建DataFrame
HDFS上的文件都在hive上,读取HDFS文件,首先就要将Spark和Hive进行集成,其次由SparkSession的spark对象需要用.enableHiveSupport()这个API,然后写spark.sql('''sql语句''')即可。
from pyspark.sql import SparkSession spark = SparkSession().Builder().appName('test').master('local[*]').enableHiveSupport().getOrCreate() ... hive_df=spark.sql(''' select * from table2 ''')
从数据库读数据创建DataFrame
利用数据库读到的表文件创建dataFrame,首先要将Spark和MySQL或Oracle进行集成,再利用spark的read的API
db_df = spark.read.format('jdbc'). \ option('url', 'jdbc:mysql://localhost:3306/DatabaseName?useSSL=false&useUnicode=true'). \ option('user', 'root'). \ option('password', '123456'). \ option('dbtable', 'tableName'). \ load()
将DataFrame注册是临时表
df2.createTempView('scores')
SQL语法风格
WordCount案例 sql.functions
电影评分数据案例(DSL语法)
-
构建入口环境
if __name__ == '__main__': spark = SparkSession.builder .appName('test') .master('local[*]') .config('spark.sql.shuffle.partitions',2) # local模式下没必要设置那么多分区,2个就可以了(默认200个) .getOrCreate() sc = spark.sparkContext
-
读取文件
schema = StructType().add('user_id',StringType(),nullable=True) .add('movie_id',IntegerType(),nullable=False) .add('rank',IntegerType(),nullable=False) .add('ts',StringType(),nullable=False) df = spark.read.format('csv') .option('sep','\t') .option('header',False) .option('encoding','utf-8') .schema(schema=schema) .load('../data/input/sql/u.data')
-
用户平均分
df.groupBy('user_id') .avg('rank') .withColumnRenamed('avg(rank)','avg_rank') .withColumn('avg_rank',F.round('avg_rank',2)) .orderBy('avg_rank',ascending=False) .show()
-
电影的平均分查询
-
查询大于平均分的电影的数量
-
查询高分电影(>3)打分次数最多的用户,此人打分的平均分
-
查询每个用户的平均打分,最低打分,最高打分
agg函数可以再里面一次完成多个聚合函数的编写
df.groupBy('user_id'). agg( F.round(F.avg('rank'),2).alias('avg_rank'), F.min('rank').alias('min_rank'), F.max('rank').alias('max_rank') ).show()
-
查询评分超过100次的电影的平均分,排名前10
shuffer 阶段分区参数设定
异常数据清洗相关的API
-
数据去重
dropDuplicates
df.dropDuplicates().show() # 无差别去重 df.dropDuplicates(['age','job']).show() # 针对age和job两列去重
-
缺失值去重
subset指定列
df.dropna().show() # 无差别删除,只要这行有空,就删除整行 df.dropna(thresh=3).show() # 该行至少有3个有效列,否则删除该行 df.dropna(thresh=2,subset=['name','age']).show() # 只对name和age这两列去重,并且有效列满足2列
-
缺失值填充
df.fillna('data_lose').show() # 将空值无差别填充为data_lose df.fillna('N/A',subset=['job']).show() # 将job列的空值填充为'N/A' df.fillna({'name':'未知姓名','age':1,'job':'worker'}) # 指定每列空值的填充内容
DataFrame数据写出到本地、HDFS
# coding:utf-8 from pyspark.sql import SparkSession,functions as F from pyspark.sql.types import StructType, StringType import pymysql from pyspark import SparkConf if __name__ == '__main__': spark = SparkSession. \ builder. \ appName('test'). \ master('local[*]'). \ config('spark.sql.shuffle.partitions', 2). \ getOrCreate() sc = spark.sparkContext # 先读csv文件构造一个df schema = StructType(). \ add('user_id', StringType(), nullable=True). \ add('subject', StringType(), nullable=True). \ add('score', StringType(), nullable=True) df = spark.read.format('csv'). \ option('sep', ','). \ option('header', False). \ option('encoding', 'utf-8'). \ schema(schema=schema). \ load('../Data/Input/scores.csv') # 以text格式写出到本地文件 # 注意:text格式写出时只能以一列的写出,所以如果文件中本来有多列,要把这多列拼接起来 df.select(F.concat_ws('--','user_id','subject','score')).\ write.mode('overwrite'). \ format('text'). \ save('../Data/Input/write_text') # 以csv格式写出 df.write.mode('overwrite').format('csv').\ option('sep',';').\ option('header',True).\ save('../Data/Input/write_csv') # 以json格式写出 df.write.mode('overwrite').format('json'). \ save('../Data/Input/write_json') # 以parquet格式写出 df.write.mode('overwrite').format('parquet'). \ save('../Data/Input/write_parquet')
DataFrame读写到数据库(mysql)
第四章
在Python中的spark sql中,只能定义UDF函数,不能定义UDAF和UDTF函数。如果非要用到UDAF和UDTF这两个函数,可以用MapPartitions算子和字典或数组类型来代替实现。
UDF创建演示
if __name__ == '__main__': # 构建执行环境入口对象SparkSesssion spark = SparkSession.builder. appName('test'). master('local[*]'). config('spark.sql.shuffle.partitions',2). getOrCreate() sc = spark.sparkContext # 构建一个rdd rdd=sc.parallelize([1,2,3,4,5,6,7]).map(lambda x:[x]) df= rdd.toDF(['num']) # TODO 1:方式1 sparksession.udf.register(), DSL和SQL风格都可以使用 # UDF的处理函数 def num_ride_10(num): return num*10 udf2 = spark.register('udf1', num_ride_10, IntegerType()) # 参数一:注册udf函数的名称,上文的udf1,仅适用于SQL风格,udf2适用于DSL风格 # 参数二:udf的处理逻辑,是一个单独的方法 # 参数三:udf的返回值类型,必须有 # 返回值的对象(udf2),这是一个udf对象,仅使用于DSL风格 # SQL风格中的使用 df.selectExpr('udf1(num)').show() # DSL风格中的使用 # 返回值udf对象(即:本文中的udf2)作为方法使用,它里面传入的参数必须是一个Column对象,但它返回后的列名还是你当时register时的列名(即:udf1) df.select(udf2(df['num'])).show() # TODO 2:方式2注册,仅适用于DSL风格 # 通过导入的functions包中的udf()这个API udf3 = F.udf(num_ride_10, IntegerType()) df.select(udf3(df['num'])).show() # DSL风 df.selectExpr('udf3(num)').show() # SQL风
注册返回值是数组类型的UDF
if __name__ == '__main__': spark = SparkSession.builder .appName('test') .master('local[*]') .config('spark.sql.shuffle.partitions',2) .getOrCreate() sc = spark.sparkContext # 构建RDD rdd = sc.paralize(['hadoop spark flink'],['hadoop spark flink']) df = rdd.toDF('line') # 注册UDF def splie_line(data): return data.split(' ') # TODO 1 udf2 = spark.udf.register('udf1', split_line, ArrayType(StringType())) # ArrayType里面的数据是什么类型的,里面还要再写需要参数的类型,这里是字符串类型 # DLS风格 df.select(udf2(df['line'])).show() # SQL风格 df.createTepmView('lines') spark.sql('select udf1(line) from lines').show(truncate=False) # 表示不截断显示,有多长展示多长 # TODO 2 udf3 = F.udf(split_line, ArrayType(StringType())) df.select(udf3(df['line'])).show(truncate=False)
返回字典类型的UDF定义
字典类型的返回值,没法直接用register来定义,它的源码里面没有定义字典类型,因此可以用StructType来接收。
if __name__ = '__main__': spark = SparkSession.builder .appName('test') .master('local[*]') .config('spark.sql.shuffle.partitions',2) .getOrCreate() sc = spark.sparkContext # 需求:传入1,返回{'num':1, 'letters':'a'} rdd = sc.parallelize([1],[2],[3]) df = rdd.toDF(['num']) def process(data): return {'num':data, 'letters':string.ascii_letters[data]} udf1 = spark.udf.register('udf1', process, StructType().add('num', IntegerType(), nullable=True) .add('letters', StringType(), nullable=True)) df.selectExpr('udf1(num)').show() df.select(udf1(df['num'])).show()
通过RDD代码模拟UDAF效果
if __name__ = '__main__': spark = SparkSession.builder .appName('test') .master('local[*]') .config('spark.sql.shuffle.partitions',2) .getOrCreate() sc = spark.sparkContext rdd = sc.parallelize([1,2,3,4,5], 3) df = rdd.map(lambda x:[x]).toDF(['num']) # 要在Python中实现UDAF的效果,可以使用rdd中的mapPartitions算子完成聚合操作 rdd1 = df.rdd.repartition(1) # 使用mapPartitions算子只能使用单分区 def process(iter): sum = 0 for row in iter: sum += row['num'] return [sum] # 一定要嵌套list,因为mapPartitions方法需求的返回值是list对象 print(rdd1.mapPartitions(process).collect())
窗口函数的演示
if __name__ = '__main__': spark = SparkSession.builder .appName('test') .master('local[*]') .config('spark.sql.shuffle.partitions',2) .getOrCreate() sc = spark.sparkContext rdd = sc.parallelize([ ('zhangsan','class_1',99), ('lisi','class_1',100), ('wangwu','class_2',80), ('zhaoliu','class_2',70), ('maqi','class_3',60 ('yangba','class_3',55) ]) schema = StructType() .add('name', StringType()) .add('class', StringType()) .add('score', IntegerType()) df = rdd.toDF(schema) df.createTempView('stu') # 聚合窗口的演示 spark.sql(r''' select *,AVG(score) as avg_score from stu ''').show() # 排序相关的窗口函数计算 spark.sql(r''' select *, row_number() over(order by score desc) as rn, dense_rank() over(partition by class order by score desc) as dr, rank() over(order by score) as r from stu ''').show() # 均分函数 ntile spark.sql(r''' select *,ntile(6) over(order by score desc) as n from stu ''').show()
总结
第五章
Catalyst优化器
SparkRDD的执行流程回顾
SparkSQL自动优化
-
原因
Catalyst优化器
断言下推就是把行变少
列值裁剪就是把列变少
SparkSql执行流程
SparkSQL执行流程
第六章
SparkOnHive原理及配置
原理
配置
-
步骤一:
-
步骤二:
-
步骤四:
在hive中集成
这个student表在hive中,只要把上面的hive配置好了,就可以直接使用spark.sql来查。
第七章
分布式SQL的执行引擎原理和配置
-
概念
-
客户端连接工具
-
代码JDBC连接
part-4 案例
写一个Spark需求的步骤
-
指定编码
#coding:utf-8
-
导包
from pyspark.sql import functions as F,SparkSession from pyspark.sql.types import StructType,DoubleType,IntegerType
-
main方法中构建spark入口对象
if __name__ == '__main__': ... spark = SparkSession. \ builder.appName('create'). \ master('local[*]'). \ config('spark.sql.shuffle.partitions', 1). \ getOrCreate() sc = spark.sparkContext
-
如果在集群运行,main方法中指定集群参数(本地则略过该步骤)
if __name__ == '__main__': ...(构建spark,sc对象) # 初始化数据库 spark.sql('use ${database}') # 设置日志级别 sc.setLogLevel('WARN') spark.sql(r'''set hive.merge.mapfiles=true''') spark.sql(r'''set hive.merge.mapredfiles=true''') spark.sql(r'''set hive.merge.size.per.task=512000000''') spark.sql(r'''set hive.merge.smallfiles.avgsize=512000000''') spark.sql(r'''set hive.exec.dynamic.partition.mode=nonstrict''') spark.conf.set("spark.sql.crossJoin.enabled", "true") main() # 业务逻辑
-
读取数据,根据spark创建Rdd对象或DataFrame对象
-
Rdd可以直接通过sc对象读取得来,也可由spark对象得来。
-
DataFrame对象可以从本地文件(spark.read)读取,可以从HDFS(spark.sql)读取,也可有rdd((rdd.toDF)转换得来,也可有pandas(spark.createDataFrame)获得
-
-
根据具体业务场景,写对应业务逻辑
-
main()方法中写业务逻辑
-
保存数据
-
-
main方法中关闭spark对象
spark.stop()
spark集群中参数解读
# 初始化数据库 spark.sql('use ${database}') # 这段代码是在 Spark SQL 中切换当前使用的数据库,其语法为:spark.sql('USE database_name')。 # 需要注意的是,使用该语句切换数据库时,需要确保指定的数据库已经存在,并且用户拥有访问该数据库的权限。如果指定的数据库不存在或用户没有访问权限,将会抛出异常。 # 设置日志级别 sc.setLogLevel('WARN') # 这段代码是用来设置 Spark 日志级别的。Spark 中的日志级别分为五个级别,分别是: # ALL:最低级别,记录所有日志信息; # DEBUG:记录详细的调试信息; # INFO:记录常规的信息,如启动信息等; # WARN:记录警告信息,表示出现了一些问题,但不会影响程序的正常运行; # ERROR:记录错误信息,表示出现了一些错误,程序可能无法正常运行。 spark.sql(r'''set hive.merge.mapfiles=true''') # 启用小文件合并功能,将多个小文件合并为一个大文件,从而减少查询时的文件数量,提高查询性能。 spark.sql(r'''set hive.merge.mapredfiles=true''') # 这个参数的作用与上一个参数类似,都是启用小文件合并功能,将多个小文件合并为一个大文件,从而提高查询性能。不同的是,这个参数使用的是 MapReduce 的方式进行文件合并 spark.sql(r'''set hive.merge.size.per.task=512000000''') # 这个参数用于控制小文件合并的最大文件大小,以字节为单位。 # 如果设置的 hive.merge.size.per.task 值较小,则 Hive 将更倾向于将多个小文件合并为一个大文件,从而减少文件数量;如果设置的值较大,则 Hive 可能会保留更多的小文件,以避免产生过大的中间结果文件。一般来说,该参数的设置需要根据具体的数据量和计算资源进行调整。 spark.sql(r'''set hive.merge.smallfiles.avgsize=512000000''') # 这个参数用于控制小文件合并的平均文件大小,以字节为单位。 spark.sql(r'''set hive.exec.dynamic.partition.mode=nonstrict''') # 这个参数用于控制 Hive 动态分区功能的模式。 # 在 Hive 中,动态分区是指在 INSERT INTO 语句中,根据数据中的某些列动态地生成分区,并将数据插入到对应的分区中。动态分区可以大大简化分区管理的工作,同时也能提高查询性能。主要有以下几种: # strict:严格模式。在这种模式下,必须使用静态分区或者完整的分区列表才能进行插入操作,否则会报错。 # nonstrict:非严格模式。在这种模式下,可以使用静态分区或者部分分区列表进行插入操作。如果插入的数据中包含了没有指定分区的字段,Hive 会自动根据数据中的字段值生成对应的分区。 # strict_all:严格全部模式。在这种模式下,必须使用完整的分区列表才能进行插入操作,否则会报错。 spark.conf.set("spark.sql.crossJoin.enabled", "true") # 这个参数用于控制 Spark SQL 中是否允许进行笛卡尔积(Cross Join)操作。笛卡尔积是一种常见的数据处理操作,它可以将两个表中的每一行都和另一个表中的每一行组合,从而生成一个新的表。但是,笛卡尔积操作的开销较大,可能会导致性能问题,因此在默认情况下,Spark SQL 是禁止进行笛卡尔积操作的。 # 需要注意的是,在进行笛卡尔积操作时,需要特别小心,避免由于数据量过大导致的性能问题或内存溢出问题。
案例背景
-
原始数据格式
-
第一步:准备数据
需求1开发
# coding:utf-8 from pyspark.sql import SparkSession if __name__ == '__main__': spark = SparkSession.builder.appName('test').master('local[*]').getOrCreate() df = spark.read.format('json').load('../../Data/Input/mini.json') df.createTempView('details') spark.sql(''' select storeProvince,sum(receivable) as money from details where receivable < 10000 and storeProvince is not null group by storeProvince order by sum(receivable) desc ''').show() >>> +--------------+------------------+ | storeProvince| money| +--------------+------------------+ | 广东省| 1713207.92334| | 湖南省|1701303.5340000002| |广西壮族自治区| 37828.22| | 北京市|10926.909999999998| | 上海市| 7358.5| | 江苏省| 6357.9| | 浙江省| 4568.1| | 山东省| 664.0| | 江西省| 553.5| | null| 20.0| +--------------+------------------+
需求2开发
需求3开发
需求4开发
# coding:utf8 from pyspark.sql import SparkSession from pyspark.sql import functions as F from pyspark.sql.types import StringType from pyspark.storagelevel import StorageLevel ''' receivable:订单金额 storeProvince:店铺省份 dataTs:订单销售日期 payType:支付类型 storeID:店铺ID ''' # 准备数据 if __name__ == '__main__': spark = SparkSession.builder. \ appName('SparkSql_sale'). \ master('local[*]'). \ config('spark.sql.shuffle.partitions', '2'). \ enableHiveSupport(). \ getOrCreate() # 读取数据 # 过滤省份字典中空值的该行数据 # 过滤省份字段中,内容为‘null’的 # 过滤订单金额大于10000的 # 列值裁剪 df = spark.read.format('json').load('../../data/input/mini.json'). \ dropna(thresh=1, subset=['storeProvince']). \ filter('storeProvince != "null"'). \ filter('receivable < 10000'). \ select('storeProvince', 'storeID', 'receivable', 'dateTS', 'payType') # TODO:需求一:每个省份的销售额统计 province_sale_df = df.groupby('storeProvince').sum('receivable'). \ withColumnRename('sum(receivable)', 'money'). \ withColumn('money', F.round('money', 2)). \ orderBy('money', ascending=False) province_sale_df.show() # 写入MySQL province_sale_df.write.mode('overwrite'). \ format('jdbc'). \ option('url', 'jdbc://node1:3306/bigdata?useSSL=false&useUnicode=true&characterEncoding=utf8'). \ option('dbtable', 'province_sale'). \ option('user', 'root'). \ option('password', '123456'). \ option('encoding', 'utf-8'). \ save() # 写入Hive province_sale_df.write.mode('overwrite'). \ saveAsTable('default.province_save', 'parquet') # TODO:需求二:销售额前3的省份中,有多少销售额到达日销售额1000+ top3_province_df = province_sale_df.limit(3).select('storeProvince'). \ withColumnRename('storeProvince', 'top3_Province') top3_province_df_joined = df.join(top3_province_df, df['storeProvince'] == top3_province_df['top3_Province'], 'inner') top3_province_df_joined.persist(StorageLevel.MEMORY_AND_DISK) province_hot_store_cnt = top3_province_df_joined.groupBy('storeProvince', 'storeID', F.from_unixtime(df['dateTS'].substr(0, 10), 'yyyy-MM-dd').alias('day')). \ sum('receivable').withColumnRename('sum(receivable)', 'money'). \ filter('money>1000'). \ dropDuplicates(subset=['storeID']). \ groupBy('storeProvince').count() # 写出MySQL,省略 # 写出hive,省略 # TODO:需求三:销售额前3的省份中,各省的平均订单价格 top3_province_order_df = top3_province_df_joined.groupBy('storeProvince'). \ avg('receivable'). \ withColumnRename('avg(receivable)', 'money'). \ withColumn('money', F.round('money', 2)). \ orderBy('money', ascending=False) top3_province_order_df.show(truncate=False) # 写出MySQL,省略 # 写出hive,省略 # TODO:需求四:销售额前3的省份中,各省支付方式的比例 # 用SQL方式实现 top3_province_df_joined.createTempView('province_pay') def udf_concat(percent): return str(round(percent * 100, 2)) + '%' my_udf = F.udf(udf_concat,StringType()) pay_type = spark.sql(''' select storeProvince,payType,(count(payType)/total) as percent from (SELECT storeProvince,payType,count(1) over(PARTITION BY storeProvince) as total FROM province_pay) as c group by storeProvince,payType,total ''').withColumn('percent',my_udf('percent')) pay_type.show() # 写出MySQL,省略 # 写出hive,省略
Part-5 Spark新特性及核心回顾
spark里面也是有Map和Reduce过程的,但并不是Map和Reduce框架,而是指Map阶段和
Reduce阶段。并且shuffle(磁盘交互)过程是无时无刻都存在的。shuffle过程是造成数据慢的主要原因。在spark中有两种shuffle,一种是HashShuffle,另一种是SortShuffle。
HashShuffleManager
在Map端,每个分区的数据先按HASH进行分组到内存中,然后再由内存落盘到磁盘上,再由磁盘文件通过网络,按照hash分组的原则,找到下一阶段对应的Reduce端,将数据发送到Redue端的内存上。
下图是HashShuffle优化后的逻辑:
优化逻辑:先在map端的内存中,将不同task端(分区)的数据先按Hash的原则,将相同内容的数据合并。结果就是在给Reduce端进行网络传输的文件数量(即网络IO的次数)大大减少了。
SortShuffleManager
总结两种shuffle
3.0新特性-AOE
新特性-动态分区裁剪 (DPP)
这个不需要我们做什么操作,只是在执行的过程中,系统自动识别,如果满足执行动态分区裁剪的条件,系统就会自动执行。
新特性-koalas库
实际用到的Spark代码解读
取数
#!/usr/bin/env python # encoding: ${encoding} # @Time: 2020/7/22 11:24 # @Auther: Jack from pyspark.sql import SparkSession import math # r'''...''' 是 Python 中的原始字符串表示法,可以防止字符串中的转义字符被解释。 def main(): spark.sql(r''' create table tab_comm_user_yj_fb_202302 as select * from tab_comm_user_yj_fb where month = 202302 ''') # 这一步相当于建临时视图 if __name__ == '__main__': # 初始化spark spark = SparkSession.Builder() .appName('spark_qs') .enableHiveSupport() # 设置 Hive 参数需要并开启 Hive 支持 .getOrCreate() sc = spark.sparkContext # 这段代码是在 Spark 中创建一个 SparkContext 对象,它是连接 SparkRDD编程入口点。在 Spark 2.x 版本及以上,可以通过 SparkSession 对象直接访问 SparkContext # 初始化数据库 spark.sql('use ${database}') # 这段代码是在 Spark SQL 中切换当前使用的数据库,其语法为:spark.sql('USE database_name')。 # 需要注意的是,使用该语句切换数据库时,需要确保指定的数据库已经存在,并且用户拥有访问该数据库的权限。如果指定的数据库不存在或用户没有访问权限,将会抛出异常。 # 设置日志级别 sc.setLogLevel('WARN') # 这段代码是用来设置 Spark 日志级别的。Spark 中的日志级别分为五个级别,分别是: # ALL:最低级别,记录所有日志信息; # DEBUG:记录详细的调试信息; # INFO:记录常规的信息,如启动信息等; # WARN:记录警告信息,表示出现了一些问题,但不会影响程序的正常运行; # ERROR:记录错误信息,表示出现了一些错误,程序可能无法正常运行。 spark.sql(r'''set hive.merge.mapfiles=true''') # 启用小文件合并功能,将多个小文件合并为一个大文件,从而减少查询时的文件数量,提高查询性能。 spark.sql(r'''set hive.merge.mapredfiles=true''') # 这个参数的作用与上一个参数类似,都是启用小文件合并功能,将多个小文件合并为一个大文件,从而提高查询性能。不同的是,这个参数使用的是 MapReduce 的方式进行文件合并 spark.sql(r'''set hive.merge.size.per.task=512000000''') # 这个参数用于控制小文件合并的最大文件大小,以字节为单位。 # 如果设置的 hive.merge.size.per.task 值较小,则 Hive 将更倾向于将多个小文件合并为一个大文件,从而减少文件数量;如果设置的值较大,则 Hive 可能会保留更多的小文件,以避免产生过大的中间结果文件。一般来说,该参数的设置需要根据具体的数据量和计算资源进行调整。 spark.sql(r'''set hive.merge.smallfiles.avgsize=512000000''') # 这个参数用于控制小文件合并的平均文件大小,以字节为单位。 spark.sql(r'''set hive.exec.dynamic.partition.mode=nonstrict''') # 这个参数用于控制 Hive 动态分区功能的模式。 # 在 Hive 中,动态分区是指在 INSERT INTO 语句中,根据数据中的某些列动态地生成分区,并将数据插入到对应的分区中。动态分区可以大大简化分区管理的工作,同时也能提高查询性能。主要有以下几种: # strict:严格模式。在这种模式下,必须使用静态分区或者完整的分区列表才能进行插入操作,否则会报错。 # nonstrict:非严格模式。在这种模式下,可以使用静态分区或者部分分区列表进行插入操作。如果插入的数据中包含了没有指定分区的字段,Hive 会自动根据数据中的字段值生成对应的分区。 # strict_all:严格全部模式。在这种模式下,必须使用完整的分区列表才能进行插入操作,否则会报错。 spark.conf.set("spark.sql.crossJoin.enabled", "true") # 这个参数用于控制 Spark SQL 中是否允许进行笛卡尔积(Cross Join)操作。笛卡尔积是一种常见的数据处理操作,它可以将两个表中的每一行都和另一个表中的每一行组合,从而生成一个新的表。但是,笛卡尔积操作的开销较大,可能会导致性能问题,因此在默认情况下,Spark SQL 是禁止进行笛卡尔积操作的。 # 需要注意的是,在进行笛卡尔积操作时,需要特别小心,避免由于数据量过大导致的性能问题或内存溢出问题。 main() spark.stop() # 用于停止SparkContext,即访问Spark功能的入口点。当调用spark.stop()方法时,它会停止所有的Spark执行器并释放SparkContext所使用的所有资源。 # 如果不调用该方法可能会有以下问题: ''' 1.Spark应用程序会一直运行,而不会停止。这将导致占用系统资源,可能会影响其他应用程序的性能。 2.SparkContext占用的资源(例如内存)将不会被释放,这可能会导致内存泄漏和资源浪费。 3.如果尝试重新创建SparkContext,可能会出现错误,因为之前的SparkContext并未完全停止。 '''
高携出小区落边界
#!/usr/bin/env python # encoding: ${encoding} # @Time: 2020/7/22 11:24 # @Auther: Jack from pyspark.sql import SparkSession import math x_pi = 3.14159265358979324 * 3000.0 / 180.0 pi = 3.1415926535897932384626 # π a = 6378245.0 # 长半轴 ee = 0.00669342162296594323 # 偏心率平方 # String, Int, Double, Double, Double, Double, Array[Double], Array[Double] def initPolygon(row): shape = str(row[1]).strip() if shape[-1:] == ';': shape = shape[:-1] # 这段代码用于判断字符串shape的最后一个字符是否为分号(;),如果是,则通过切片操作去掉该字符。 # 具体来说,shape[-1:]表示取字符串shape的最后一个字符,shape[:-1]表示取字符串shape的第一个字符到倒数第二个字符(不包括最后一个字符),即去掉最后一个字符。 polyBounds = shape.split('\n')[0].split(';') # 该代码首先使用split('\n')方法将多边形边界数据按照换行符分割成多个行(列表中的元素),然后使用[0]取第一个行(多边形的轮廓线),最后使用split(';')方法将轮廓线按照分号分割成多个点(列表中的元素)。 pointNum = len(polyBounds) pointLng = [0] * (pointNum + 1) # 该代码使用了列表复制的技巧,即使用[0] * n的方式来初始化一个长度为n的列表,其中每个元素的值都是0。在这里,pointNum+1表示需要初始化的列表的长度,该代码的作用是为存储多边形各个点的经度坐标初始化一个列表,并将该列表的每个元素初始化为0 pointLat = [0] * (pointNum + 1) for i in range(0, pointNum): temp = polyBounds[i].split(',') pointLng[i] = float(temp[0]) pointLat[i] = float(temp[1]) pointLng[pointNum] = pointLng[0] pointLat[pointNum] = pointLat[0] # 将列表pointLng和pointLat中的最后一个元素设置为第一个元素,以便将多边形闭合。 return [str(row[0]), pointNum + 1, float(max(pointLng)), float(min(pointLng)), float(max(pointLat)),float(min(pointLat)), pointLng, pointLat] # 将多边形的相关信息以列表形式返回,包括多边形的ID、点的数量、经度最大值、最小值、纬度最大值、最小值以及点的经度坐标和纬度坐标。 # PNPoly算法实现 def pointInPolygon(points, lng, lat, boundsLng, boundsLat): flag = False j = 0 for i in range(0, points): if i == 0: j = points - 1 else: j = i - 1 if (((boundsLat[i] > lat) != (boundsLat[j] > lat)) and ( lng < (boundsLng[j] - boundsLng[i]) * (lat - boundsLat[i]) / (boundsLat[j] - boundsLat[i]) + boundsLng[i])): flag = not flag return flag # 主要计算函数,使用的poly数据结构为 # poly: Array[(String, Int, Double, Double, Double, Double, Array[Double], Array[Double])], # row: (String, Double, Double) def compute(poly, row): result = [] for i in range(0, len(poly)): # if float(str(poly[i][3])) <= float(str(row[1])) <= float(str(poly[i][2])) and float(str(poly[i][5])) <= float(str(row[2])) <= float(str(poly[i][4])): if poly[i][3] <= row[1] <= poly[i][2] and poly[i][5] <= row[2] <= poly[i][4]: if pointInPolygon(poly[i][1], row[1], row[2], poly[i][6], poly[i][7]): result.append(row[0] + '|' + poly[i][0]) return result def bx_zsmr_region2grid_200_d(): # 获取经纬度边界,这个地方放高携出小区的经纬度 polygon_df = spark.sql(r''' select REGION_FLAG as id,ORIGIN_LAT_LON as shape from qs_wgg_gxc_yjsp where ORIGIN_LAT_LON is not null ''') print('小区边界读取完毕') polygon_df = sc.broadcast(polygon_df.rdd.map(lambda x: initPolygon(x)).collect()) print('小区边界读取完毕1') # 获取经纬度数据 要求经纬度为数字格式并不为空 # 这里放沿街商铺的表,要把表抽到hive库,因为这个程序是在HIVe库跑 print('开始经纬度读取完毕') grid_df = spark.sql(r''' select custid as id,cast(longitude as double) as lng,cast(latitude as double) as lat from qs_wg_ql_20221227 where CUSTID is not null and longitude is not null and latitude is not null ''').repartition(1000) print('经纬度读取完毕') res_rdd = grid_df.rdd.flatMap(lambda x: compute(polygon_df.value, x)).map( lambda x: [x.split('|')[0], x.split('|')[1]]) # 这行代码的作用是对经纬度数据表中的每个记录计算其所属的小区边界,并将结果转化为一个RDD,其中每个元素都是一个列表,包含两个元素:第一个元素是经纬度记录的id,第二个元素是其所属的小区边界的id。具体来说,这行代码首先调用了grid_df的rdd方法将数据转化为RDD,然后使用flatMap方法对每个经纬度记录调用compute函数,计算其所属的小区边界,最后使用map方法将结果转化为所需格式的RDD。 res_df = spark.createDataFrame(res_rdd, ['id', 'region_id']).repartition(100) # 这一行代码将上一行代码生成的RDD转换为DataFrame,其中'id'和'region_id'分别为DataFrame的两列,并将数据分为100个分区(即100个文件)。 res_df.createOrReplaceTempView('temp_changjing_result') # 这行代码将DataFrame注册为一个临时表,可以在Spark SQL中使用该表进行后续的查询和操作。在这里,临时表的名称为 temp_changjing_result。 print('step result_1 is OK') spark.sql(r'''drop table if exists temp_changjing_${taskid}_result''') spark.sql(r''' create table temp_changjing_${taskid}_result select * from temp_changjing_result ''') # 这段代码是创建一个名为temp_changjing_${taskid}_result的Hive表,并将temp_changjing_result中的数据插入到该表中。这段代码的作用是将前面处理得到的结果保存到Hive表中,以便后续使用或查询。 def main(): bx_zsmr_region2grid_200_d() # main函数是程序的主入口,通常会调用其他函数或模块来完成具体的任务,是程序的逻辑枢纽。在这段代码中,main函数只调用了一个函数bx_zsmr_region2grid_200_d()来执行具体的计算任务,其作用是将小区边界和经纬度数据读入,并计算经纬度数据所在的小区。 if __name__ == '__main__': # 初始化spark spark = SparkSession.Builder().appName('jwd_bj_qs_wgg_gxc_yjsp').enableHiveSupport().getOrCreate() sc = spark.sparkContext # 初始化数据库 spark.sql('use ${database}') # 设置日志级别 sc.setLogLevel('WARN') spark.sql(r'''set hive.merge.mapfiles=true''') spark.sql(r'''set hive.merge.mapredfiles=true''') spark.sql(r'''set hive.merge.size.per.task=512000000''') spark.sql(r'''set hive.merge.smallfiles.avgsize=512000000''') spark.sql(r'''set hive.exec.dynamic.partition.mode=nonstrict''') spark.conf.set("spark.sql.crossJoin.enabled", "true") main() spark.stop()
百度经纬度转WGS84经纬度
#!/usr/bin/env python # encoding: ${encoding} # @Time: 2023/3/14 11:22 # @Auther: Dahua # -*- coding: utf-8 -*- from pyspark.sql import SparkSession from pyspark.sql.types import StructType, StructField, StringType, DoubleType import json import urllib import math # import numpy as np x_pi = 3.14159265358979324 * 3000.0 / 180.0 pi = 3.1415926535897932384626 # π a = 6378245.0 # 长半轴 ee = 0.00669342162296594323 # 偏心率平方 ''' 输入(经度,维度) ''' def bd09_to_gcj02(bd_lon, bd_lat): """ 百度坐标系(BD-09)转火星坐标系(GCJ-02) 百度——>谷歌、高德 :param bd_lat:百度坐标纬度 :param bd_lon:百度坐标经度 :return:转换后的坐标列表形式 """ x = bd_lon - 0.0065 y = bd_lat - 0.006 z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * x_pi) theta = math.atan2(y, x) - 0.000003 * math.cos(x * x_pi) gg_lng = z * math.cos(theta) gg_lat = z * math.sin(theta) return [gg_lng, gg_lat] def gcj02_to_wgs84(lng, lat): """ GCJ02(火星坐标系)转GPS84 :param lng:火星坐标系的经度 :param lat:火星坐标系纬度 :return: """ if out_of_china(lng, lat): return [lng, lat] dlat = _transformlat(lng - 105.0, lat - 35.0) dlng = _transformlng(lng - 105.0, lat - 35.0) radlat = lat / 180.0 * pi magic = math.sin(radlat) magic = 1 - ee * magic * magic sqrtmagic = math.sqrt(magic) dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi) dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi) mglat = lat + dlat mglng = lng + dlng return [lng * 2 - mglng, lat * 2 - mglat] def gcj02_to_bd09(lng, lat): """ 火星坐标系(GCJ-02)转百度坐标系(BD-09) 谷歌、高德——>百度 :param lng:火星坐标经度 :param lat:火星坐标纬度 :return: """ z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * x_pi) theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * x_pi) bd_lng = z * math.cos(theta) + 0.0065 bd_lat = z * math.sin(theta) + 0.006 return [bd_lng, bd_lat] def wgs84_to_gcj02(lng, lat): """ WGS84转GCJ02(火星坐标系) :param lng:WGS84坐标系的经度 :param lat:WGS84坐标系的纬度 :return: """ if out_of_china(lng, lat): # 判断是否在国内 return [lng, lat] dlat = _transformlat(lng - 105.0, lat - 35.0) dlng = _transformlng(lng - 105.0, lat - 35.0) radlat = lat / 180.0 * pi magic = math.sin(radlat) magic = 1 - ee * magic * magic sqrtmagic = math.sqrt(magic) dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi) dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi) mglat = lat + dlat mglng = lng + dlng return [mglng, mglat] def wgs84_to_bd09(lon, lat): lon, lat = wgs84_to_gcj02(lon, lat) return gcj02_to_bd09(lon, lat) def out_of_china(lng, lat): """ 判断是否在国内,不在国内不做偏移 :param lng: :param lat: :return: """ return not (lng > 73.66 and lng < 135.05 and lat > 3.86 and lat < 53.55) def _transformlng(lng, lat): ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + \ 0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng)) ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(lng * pi) + 40.0 * math.sin(lng / 3.0 * pi)) * 2.0 / 3.0 ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 * math.sin(lng / 30.0 * pi)) * 2.0 / 3.0 return ret def _transformlat(lng, lat): ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + \ 0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng)) ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(lat * pi) + 40.0 * math.sin(lat / 3.0 * pi)) * 2.0 / 3.0 ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 * math.sin(lat * pi / 30.0)) * 2.0 / 3.0 return ret def bd09_to_wgs84(row): lon, lat = bd09_to_gcj02(row[1],row[2]) lonwgs,latwgs=gcj02_to_wgs84(lon, lat) return [row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],row[8],row[9],row[10],lonwgs,latwgs] def main(): grid_df = spark.sql(r''' select zydmc, cast(bdjd as double) as bdjd, cast(bdwe as double) as bdwe, sfjzldtkcj, tkcjmc, fgs, wg, jd, large_category, medium_category, small_category from temp_zyd_jjl_20230228 ''').repartition(1000).cache() # DataFrame对象将被缓存到内存中,以便在后续操作中更快地访问数据。 res_rdd = grid_df.rdd.map(lambda row : bd09_to_wgs84(row)) schema = StructType([ StructField("zydmc", StringType(), True), StructField("bdjd", DoubleType(), True), StructField("bdwe", DoubleType(), True), StructField("sfjzldtkcj", StringType(), True), StructField("tkcjmc", StringType(), True), StructField("fgs", StringType(), True), StructField("wg", StringType(), True), StructField("jd", StringType(), True), StructField("large_category", StringType(), True), StructField("medium_category", StringType(), True), StructField("small_category", StringType(), True), StructField("lonwgs", DoubleType(), True), StructField("latwgs", DoubleType(), True) ]) res_df = spark.createDataFrame(res_rdd, schema) # 该方法用来创建DataFrame对象,res_rdd表示输入数据,schema是DataFrame的模式,如果不指定,则会尝试自动推断。 res_df.coalesce(10).write.mode("overwrite").saveAsTable("temp_zyd_jjl_20230228_1") if __name__ == '__main__': # 初始化spark spark = SparkSession.Builder().appName('jwd_bd09_to_wgs84_1').enableHiveSupport().getOrCreate() sc = spark.sparkContext # 初始化数据库 spark.sql('use ${database}') # 设置日志级别 sc.setLogLevel('WARN') main() spark.stop()
备注:
if __name__ == "__main__": main() 这段代码的意思是,如果当前脚本被作为主程序执行,那就调用main()函数。如果当前脚本被作为模块导入到其他脚本,那么main()函数不会被执行。 使用if __name__ == "__main__"语句判断当前脚本是否作为主程序,如果是,就调用main()函数
Spark SQL核心编程
核心对象是SparkSession,其实就是SparkCore中SparkContext的升级版本。
DataFrame的创建(SparkSQL语法)
//读取Json文件创建DataFrame val df = spark.read.json("data/user.json") //对DataFrame创建一个临时表 df.createOrReplaceTempView("people") //这个视图是不能跨越Session的,如果像全局有效,可以像下面创建全局临时表。 //通过SQL语句实现全表查询 val sqlDF = spark.sql("select * from people") sqlDF.show //对于DataFrame创建一个全局表 df.createGlobeTempView("people") //通过sql语句实现全表查询 spark.sql("select * from global_temp.people").show() //使用全局临时表需要全路径访问,如:global_temp.people
DSL语法
//用法与RDD中用法非常类似,因为DataFrame有数据和结构信息,可以直接当元数据(类似RDD)来用 //创建一个DataFrame val df = spark.read.json("data/user.json") //查看DataFrame的Schema信息 df.printSchema //只查看“username”列数据 df.select("username").show() //查看“username”列数据以及“age+1”数据 df.select($"username",$"age"+1).show //写法一,$表示引用 df.select(`username,`age + 1 as "newage").show() //写法二,`也表示引用 //查看“age"大于30的数据 df.filter($"age">30).show //按照age分组,查看数据条数 df.groupBy("age").count.show
RDD,DataFrame,DataSet三者的转换