简介
- 专为大规模数据处理而设计的快速通用计算引擎(与
Hadoop
的MapReduce
功能类似) - 可以导入外部存储系统的数据集,例如:
HDFS
、HBase
或其他Hadoop
数据源 - 基于内存的分布式计算框架(只计算,不涉及存储)
Spark
框架组件丰富- 批处理:
Spark core
、Spark SQL
- 交互式计算:
Spark SQL
- 流式计算:
Spark Streaming
- 机器学习:
Spark ML/Spark MLLib
- 批处理:
spark
的缺点:内存消耗大,不太稳定(相较于MapReduce
)spark
的优点:- 速度快(比
mapreduce
在内存中快100倍,在磁盘中快10倍) - 易用性(可以通过
java/scala/python/R
开发spark
应用程序) - 通用性(可以使用
spark sql/spark streaming/mlib/Graphx
) - 兼容性(
spark
程序可以运行在standalone/yarn/mesos
)
- 速度快(比
开启spark
local 模式(用于调试代码)
进入路径spark/python/
直接执行:pyspark --master local[线程数]
;执行pyspark
表示开一个线程
执行结果:
集群模式
进入路径spark/sbin/
启动命令:./start-all.sh
关闭命令:./stop-all.sh
当执行jps
时:包含Master
和Worker
表示开启成功
spark-core(RDD)
RDD
(Resilient Distributed Dataset
)叫做弹性分布式数据集,是Spark
中最基本的数据抽象,它代表一个 不可变 、 可分区 、里面的元素可 并行计算 的数据集合.
Dataset
数据集:一个数据集,简单的理解为集合,用于存放数据Distributed
分布式存储:RDD
的数据是分布式存储,并且可以做分布式的计算Resilient
:弹性- 它的数据可以保存在磁盘,也可以保存在内存中
- 数据分布式也是弹性的
- 不可变:
RDD1
变换为RDD2
时RDD1
依然存在 - 可分区:多个分区,数据可以存储在多个不同的机器内存上
- 并行计算
RDD
特性:
- 分区:数据可以存储在不同的机器内存上
- 依赖:每一步计算都依赖于上一步计算的结果
- 缓存:数据可以利用灵活的缓存策略进行存储
- 默认缓存到内存
- 对于
k:v 型 RDD
具有该分区特性(内存,磁盘,内存和磁盘)
RDD
的核心属性:调度和计算都依赖于这五个属性
- 分区列表:多个分区放在一起,便于管理
- 依赖列表:记录着每一个计算中的依赖关系
Compute
函数,用于计算RDD
各分区的值- 分区策略(可选):根据自定义策略可以调整分区大小,分区个数。
- 优先位置列表(可选,HDFS实现数据本地化,避免数据移动)
RDD的 常用算子
RDD
的三类算子包括 transformation
、action
和 persist
,
- 前两种进行数据处理
persist
处理数据存储相关操作
transformation
- 通过已有的
RDD
生成一个新的RDD
- 懒计算:只会记录
RDD
转化关系,并不立即计算,当执行action操作
时才会计算 - 举例:
map
,filter
,groupBy
,reduceBy
- 优点:可以中间插入优化过程
value型 transformation
-
map
: 一对一映射rdd
元素
格式:RDD对象. map(函数名 或 匿名函数)
作用:将指定函数
作用到RDD数据集
的每一个元素上,生成一个新的RDD
返回
使用匿名函数 例 1 >>> rdd1 = sc.parallelize([3,1,2,5,5],2) >>> rdd2 = rdd1.map(lambda x: x+1) >>> rdd2.collect() # collect()函数是 action算子 的一种 用于显示RDD内容 [4, 2, 3, 6, 6] 例 2 >>> rdd4 = rdd1.map(lambda x: (x,1)) >>> rdd4.collect() [(3,1), (1,1), (2,1), (5,1), (5,1)] 例 3 >>> rdd1 = sc.parallelize(['华','明','亮'],2) >>> rdd2 = rdd1.map(lambda x: '小' + x) >>> rdd2.collect() ['小华', '小明', '小亮'] 先定义函数 再调用 例: >>> def addOne(x): return x+1 >>> rdd2 = rdd1.map(addOne) >>> rdd2.collect() [4, 2, 3, 6, 6]
-
mapPartitions
分区式执行map
函数
格式:RDD对象. mapPartitions(函数名 或 匿名函数)
作用:类似于map
,map
作用于每个分区的每个元素,但mapPartitions
作用于每个分区
例如:rdd
有N
个元素,分布于M
个分区,若用map
则函数会调用N
次, 而使用mapPartitions
函数仅调用M
次,当在映射的过程中不断地创建对象时就可以使用mapPartitions
,比map
的效率要高很多。比如当向数据库写入数据 -
filter
: 过滤rdd
中的元素
格式:RDD对象. filter(函数名 或 匿名函数)
作用:选出所有 执行指定函数
返回值为true
的元素,生成一个新的RDD
返回>>> rdd1 = sc.parallelize([1,2,3,4,5,6,7,8,9],3) >>> rdd2 = rdd1.map(lambda x:x*2) >>> rdd3 = rdd2.filter(lambda x:x>4) >>> rdd3.collect() [6, 8, 10, 12, 14, 16, 18] >>> rdd4 = rdd1.filter(lambda x: x % 2 == 0).collect() [2, 4, 6, 8] >>> rdd4 = rdd1.filter(lambda x: x in [2,7,12.20]).collect() [2, 7] >>> rdd4 = rdd2.filter(lambda x: '2' in str(x)).collect() ['2', '12']
-
flatmap
: 一对多映射rdd
元素
格式:RDD对象. flatmap(函数名 或 匿名函数)
作用:会先执行map
的操作,再将所有数据集合并为一个数据集>>> rdd1 = sc.parallelize(["a b c","d e f","h i j"]) >>> rdd2 = rdd1.flatMap(lambda x:x.split(" ")) >>> rdd2.collect() ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i', 'j']
flatmap
和map
的区别:flatmap
在map
的基础上将结果合并到一个列表中>>> rdd1 = sc.parallelize(["a b c","d e f","h i j"]) >>> rdd2 = rdd1.map(lambda x:x.split(" ")) >>> rdd2.collect() [['a', 'b', 'c'], ['d', 'e', 'f'], ['h', 'i', 'j']]
注意:若
map
的结果列表中没有容器类型数据(即不可再拆),则flatmap
会出错(即flatmap
输出的rdd
元素个数一定比原rdd
元素个数多) -
flatMapValues
: 一对多映射rdd
元组元素中的value列表
通过flatMap
函数传递键值对RDD
中的每个值,而无需更改键;这也保留了原始RDD
的分区。>>> x = sc.parallelize([("a", ["x", "y", "z"]), ("b", ["p", "r"])]) >>> def f(x): return x >>> x.flatMapValues(f).collect() [('a', 'x'), ('a', 'y'), ('a', 'z'), ('b', 'p'), ('b', 'r')]
-
union
: 合并两rdd
中的元素
格式:RDD1.union(RDD2)
作用:对两个RDD求并集>>> rdd1 = sc.parallelize([("a",1),("b",2)]) >>> rdd2 = sc.parallelize([("c",1),("b",3)]) >>> rdd3 = rdd1.union(rdd2) >>> rdd3.collect() [('a', 1), ('b', 2), ('c', 1), ('b', 3)]
-
intersection
: 求两个rdd
的交集元素
格式:RDD1.intersection(RDD2)
作用:对两个RDD求交集>>> rdd1 = sc.parallelize([("a",1),("b",2)]) >>> rdd2 = sc.parallelize([("c",1),("b",3)]) >>> rdd3 = rdd1.union(rdd2) >>> rdd4 = rdd3.intersection(rdd2) >>> rdd4.collect() [('c', 1), ('b', 3)]
-
distinct
: 去重rdd
中的元素
格式:distinct(numPartitions = None )
作用:去重,返回一个包含该RDD
中不同元素的新RDD
>>> sorted(sc.parallelize([1, 1, 2, 3]).distinct().collect()) [1, 2, 3]
-
sample
采样数据
格式:rdd1.sample(抽样是否放回,采样比例,随机种子)
rdd2 = rdd.sample(False,0.2,100)
-
glom
将分区的元素组成一个数据
key-value型 transformation
以下命令处理的rdd
一定是 二元素元组 的列表,并根据第0个元素(key
)操作第1个元素(value
)
-
groupByKey
: 分组
作用:以元组中的第0个元素作为key
,进行分组,返回一个新的RDD
注意:groupByKey
结果为( Key,value)
,value
是一个可迭代的对象>>> rdd1 = sc.parallelize([("a",1),("b",2)]) >>> rdd2 = sc.parallelize([("c",1),("b",3)]) >>> rdd3 = rdd1.union(rdd2) >>> rdd4 = rdd3.groupByKey() >>> rdd4.collect() [('a', <pyspark.resultiterable.ResultIterable object at 0x7fba6a5e5898>), ('c', <pyspark.resultiterable.ResultIterable object at 0x7fba6a5e5518>), ('b', <pyspark.resultiterable.ResultIterable object at 0x7fba6a5e5f28>)] >>> result[2] ('b', <pyspark.resultiterable.ResultIterable object at 0x7fba6c18e518>) >>> result[2][1] <pyspark.resultiterable.ResultIterable object at 0x7fba6c18e518> >>> list(result[2][1]) [2, 3]
-
reduceByKey
:聚合
格式:RDD1.reduceByKey(函数名 或 匿名函数)
作用:将key
相同的键值对,按照指定函数
进行计算
注意:如果分组是为了对每个键执行聚合(例如求和或平均值),使用reduceByKey
性能更好>>> rdd = sc.parallelize([("a", 1), ("b", 1), ("a", 1)]) >>> rdd.reduceByKey(lambda x,y:x+y).collect() [('b', 1), ('a', 2)]
-
sortByKey
根据key
排序
格式:rdd1.sortByKey(ascending=True, numPartitions=None, keyfunc = lambda x : x )
作用:对该RDD排序,假定该RDD由(键,值)对组成>>> tmp = [('a', 1), ('b', 2), ('1', 3), ('d', 4), ('2', 5)] >>> sc.parallelize(tmp).sortByKey().first() ('1', 3) >>> sc.parallelize(tmp).sortByKey(True, 1).collect() [('1', 3), ('2', 5), ('a', 1), ('b', 2), ('d', 4)] >>> sc.parallelize(tmp).sortByKey(True, 2).collect() [('1', 3), ('2', 5), ('a', 1), ('b', 2), ('d', 4)] >>> tmp2 = [('Mary', 1), ('had', 2), ('a', 3), ('little', 4), ('lamb', 5)] >>> tmp2.extend([('whose', 6), ('fleece', 7), ('was', 8), ('white', 9)]) >>> sc.parallelize(tmp2).sortByKey(True, 3, keyfunc=lambda k: k.lower()).collect() [('a', 3), ('fleece', 7), ('had', 2), ('lamb', 5),...('white', 9), ('whose', 6)]
-
sortby
排序 -
join
格式:RDD1.join(RDD2,numPartitions = None )
作用:当调用类型(K,v1)
和(K,v2)
的数据集时,返回(K,(v1,v2))
对的数据集包含每个键的所有元素对>>> x = sc.parallelize([("a", 1), ("b", 4)]) >>> y = sc.parallelize([("a", 2), ("a", 3)]) >>> sorted(x.join(y).collect()) [('a', (1, 2)), ('a', (1, 3))]
-
leftOuterJoin
:执行左外连接的rdd1
和rdd2
格式:RDD1.leftOuterJoin(RDD2,numPartitions = None )
作用:以左侧RDD1
的数据为基准,将RDD2
中的相同key
的值组合,若RDD2
中不包含RDD1
中的key
,则组合时使用None
组合>>> x = sc.parallelize([("a", 1), ("b", 4)]) >>> y = sc.parallelize([("a", 2). ("a", 3), ("d", 2)]) >>> sorted(x.leftOuterJoin(y).collect()) [('a', (1, 2)), ('a', (1, 3)), ('b', (4, None))]
-
mapvalue
对键值对中的值进行map
操作
格式:RDD1.mapvalue(匿名函数或函数名)
x = sc.parallelize([("a", ["apple", "banana", "lemon"]), ("b", ["grapes"])]) x.mapValues(lambda x:len(x)).collect() [('a', 3), ('b', 1)]
action
- 通过
RDD
计算得到一个或者一组值 Action操作
是立即执行的- 举例:
count
,reduce
,saveAsTextFile
- 缺点:不能插入优化过程
-
collect
作用:返回一个list
,list
中包含RDD
中的所有元素
只有当数据量较小的时候使用
所有的结果都会加载到内存中 -
reduce
作用:reduce
将RDD
中元素两两传递给输入函数,同时产生一个新的值,新产生的值与RDD
中下一个元素再被传递给输入函数直到最后只有一个值为止。>>> rdd1 = sc.parallelize([1,2,3,4,5]) >>> rdd1.reduce(lambda x,y : x+y) 15
-
first
作用:返回RDD的第一个元素>>> sc.parallelize([2, 3, 4]).first() 2
-
take
作用:返回RDD的前N个元素
格式:RDD.take(num)
>>> sc.parallelize([2, 3, 4, 5, 6]).take(2) [2, 3] >>> sc.parallelize([2, 3, 4, 5, 6]).take(10) [2, 3, 4, 5, 6] >>> sc.parallelize(range(100), 100).filter(lambda x: x > 90).take(3) [91, 92, 93]
-
count
作用:返回RDD中元素的个数>>> sc.parallelize([2, 3, 4]).count() 3
-
takeOrdered
格式:RDD.takeOrdered(num,key = None )
作用:从RDD
中以升序或可选键函数指定的顺序获取N
个元素。
注意:仅当预期结果数组较小时才应使用此方法,因为所有数据均已加载到驱动程序的内存中>>> sc.parallelize([10, 1, 2, 9, 3, 4, 5, 6, 7]).takeOrdered(6) [1, 2, 3, 4, 5, 6] >>> sc.parallelize([10, 1, 2, 9, 3, 4, 5, 6, 7], 2).takeOrdered(6, key=lambda x: -x) [10, 9, 7, 6, 5, 4]
-
saveAsTextFile
格式:rdd.saveAsTextFile('HDFS文件路径/文件名')
作用:函数将RDD保存为文本至HDFS指定目录,每次输出一行,每个分区存储一个文件 -
broadcast
广播变量
作用:将存储在单节点内存中数据共享,避免数据重复复制
设置:广播变量名 = sparkContext.broadcast(要共享的数据)
获取:广播变量名.value
persist
persist
操作用于将数据缓存 可以缓存在内存中 也可以缓存到磁盘上, 也可以复制到磁盘的其它节点上
cache
:缓存到内存Persist
:更灵活的缓存策略cache()
方法调用的也是persist
方法,缓存策略均为MEMORY_ONLY
- 可以通过
persist
方法手工设定StorageLevel
来满足工程需要的存储级别
关闭spark
代码:spark对象.stop()
案例
根据日志中的ip获取个经纬度地区的访问数量
1、 加载城市ip段信息,获取ip起始数字和结束数字,经度,纬度
2、 加载日志数据,获取ip信息,然后转换为数字,和ip段比较
3、 比较的时候采用二分法查找,找到对应的经度和纬度
4,对相同的经度和纬度做累计求和
代码
from pyspark.sql import SparkSession
from pyspark import SparkContext
def ip_transform(ip):
ips = ip.split(".")#[223,243,0,0] 32位二进制数
ip_num = 0
for i in ips:
ip_num = int(i) | ip_num << 8
return ip_num
# 255.255.255.255 0~255 256 2^8 8位2进制数 32位2进制数
# 将ip转换为特殊的数字形式 223.243.0.0|223.243.191.255| 255 2^8
# int(i) | ip_num << 8
# 11011111 ip_num 的二进制(ip的第一段数字)
# 1101111100000000 ip_num << 8 二进制左移八位
# 11110011 i 的二进制
# 1101111111110011 | 表示 二进制的与操作
# 11011111111100111101111111110011 最终将ip转化为一个32位的二进制数
#二分法查找ip对应的行的索引
def binary_search(ip_num, broadcast_value):
start = 0
end = len(broadcast_value) - 1
while (start <= end):
mid = int((start + end) / 2)
if ip_num >= int(broadcast_value[mid][0]) and ip_num <= int(broadcast_value[mid][1]):
return mid
if ip_num < int(broadcast_value[mid][0]):
end = mid-1
if ip_num > int(broadcast_value[mid][1]):
start = mid+1
def main():
spark = SparkSession.builder.appName("xxx").getOrCreate()
sc = spark.sparkContext
city_id_rdd = sc.textFile("file:///root/tmp/ip.txt").map(lambda x:x.split("|")).map(lambda x: (x[2], x[3], x[13], x[14]))
#创建一个广播变量
city_broadcast = sc.broadcast(city_id_rdd.collect())
dest_data = sc.textFile("file:///root/tmp/20090121000132.394251.http.format").map(
lambda x: x.split("|")[1])
#根据取出对应的位置信息
def get_pos(x):
city_broadcast_value = city_broadcast.value
#根据单个ip获取对应经纬度信息
def get_result(ip):
ip_num = ip_transform(ip)
index = binary_search(ip_num, city_broadcast_value)
#((纬度,精度),1)
return ((city_broadcast_value[index][2], city_broadcast_value[index][3]), 1)
x = map(tuple,[get_result(ip) for ip in x])
return x
dest_rdd = dest_data.mapPartitions(lambda x: get_pos(x)) #((纬度,精度),1)
result_rdd = dest_rdd.reduceByKey(lambda a, b: a + b)
print(result_rdd.collect())
sc.stop()
if __name__ == '__main__':
main()
Spark SQL (DataFrame)
Spark
中用于处理结构化数据的一个模块- 抛弃原有
Shark
的代码,但汲取了其部分优点,如内存列存储、Hive
兼容性等 - 用于替代
spark RDD
代码,不仅代码量更少,速度还更快
Spark SQL
的特性:
- 注意想要使用
SQL语句
操作DataFrom
必须先将DataFrom
创建成视图DF.createOrReplaceTempView("视图名") resultDF = spark.sql("select * from 视图名 where pop>4000")
Integrated
易整合:可以在Spark
程序中无缝加入SQL
查询,可以使用SQL
语句 也可以使用DataFrame API
results = spark.sql("SELECT * FROM people") names = results.map(lambda p: p.name)
Uniform Data Access
统一的数据访问: 使用相同的方式连接不同的数据源,包括(Hive
,Avro
,Parquet
,ORC
,JSON
, 和JDBC
).spark.read.json("s3n://...").registerTempTable("json") results = spark.sql( """SELECT * FROM people JOIN json ...""")
Hive Integration
兼容hive
:在已有的数据仓库中执行SQL
或者HiveQL
查询,Spark SQL
支持HQL
语法以及自定义函数(直接接入Hive
的metaStore
)Standard Connectivity
标准接口:提供了JDBC
或ODBC
的数据接口,作为数据服务将数据通过JDBC/ODBC
方式传递
DataFrame简介
在Spark
语义中,DataFrame
是一个分布式的行集合,可以想象为一个关系型数据库的表
特点:
- 不可变:一旦
DataFrame
被创建,就不能更改,只能生成新的DataFrame
- 懒加载:只有
action
才会触发Transformation
的执行
DataFrame常用算子
DataFrame
的API
也分为 Transformation
和 Action
两类
-
printSchema
显示数据结构
格式:DataFlame.printSchema()
>>> schemaPeople.printSchema() root |-- age: long (nullable = true) |-- name: string (nullable = true)
-
show
格式:DataFlame.show(n)
作用:显示前n
条数据>>> schemaPeople.show(2, truncate=False) # 完整(当列值太长时会省略显示)显示两条数据 +---+--------+ |age| name| +---+--------+ | 25| Ankit| | 22|Jalfaizy| +---+--------+
-
count
格式:DataFlame.count()
作用:统计DataFlame
总行数>>> schemaPeople.count() 4
-
columns
格式:DataFlame.columns
作用:统计DataFlame
总行数>>> schemaPeople.columns ['age', 'name']
-
withColumn
增加(或 替换)一列
格式:withColumn(列名,类表达式)
如果列名已存在,则表示替换原有列的数据>>> df = schemaPeople.withColumn("age2", schemaPeople.age * 2) >>> df.show() +---+--------+----+ |age| name|age2| +---+--------+----+ | 25| Ankit| 50| | 22|Jalfaizy| 44| | 20| saurabh| 40| | 26| Bala| 52| +---+--------+----+ # 修改数据类型并重命名 >>> df.withColumn("user", df.user.cast(IntegerType())).withColumnRenamed("user", "userId")
-
drop
删除一列
格式:DataFlame.drop(指定列名)
如果指定列名不存在,则不删列不报错>>> df.drop("age").show() +--------+----+ | name|age2| +--------+----+ | Ankit| 50| |Jalfaizy| 44| | saurabh| 40| | Bala| 52| +--------+----+
-
describe
计算统计信息
格式:DataFlame.describe(指定列名)
如果不指定列名,则计算所有数字和字符串列的统计信息
统计信息包括:计数,平均值,标准差,最小值和最大值>>> df.describe().show() +-------+-----------------+-------+-----------------+ |summary| age| name| age2| +-------+-----------------+-------+-----------------+ | count| 4| 4| 4| | mean| 23.25| null| 46.5| | stddev|2.753785273643051| null|5.507570547286102| | min| 20| Ankit| 40| | max| 26|saurabh| 52| +-------+-----------------+-------+-----------------+ >>> df.describe("age").show() +-------+-----------------+ |summary| age| +-------+-----------------+ | count| 4| | mean| 23.25| | stddev|2.753785273643051| | min| 20| | max| 26| +-------+-----------------+
-
select
提取指定列
格式:DataFlame.select(指定列名1, 列名2,...)
>>> df.select("name", "age").show() +--------+---+ | name|age| +--------+---+ | Ankit| 25| |Jalfaizy| 22| | saurabh| 20| | Bala| 26| +--------+---+
distinct
去重统计"cls"列 中 有多少个不重复值 df.select('cls').distinct().count()
-
dropDuplicates
删除指定列重复的行数据
格式:DataFlame.dropDuplicates([列1,列2,...])
若不指定列则表示删除所有列皆重复的行数据>>> from pyspark.sql import Row >>> df = sc.parallelize([ \ ... Row(name='Alice', age=5, height=80), \ ... Row(name='Alice', age=5, height=80), \ ... Row(name='Alice', age=10, height=80)]).toDF() >>> df.dropDuplicates().show() +---+------+-----+ |age|height| name| +---+------+-----+ | 5| 80|Alice| | 10| 80|Alice| +---+------+-----+ >>> df.dropDuplicates(['name', 'height']).show() +---+------+-----+ |age|height| name| +---+------+-----+ | 5| 80|Alice| +---+------+-----+
-
dropna
删除空值超过指定个数的行数据
格式:dropna(how ='any',thresh = None,subset = None )
-
how
:any 或 all
。any
表示 有Null值
就删 行数据。all
表示 全Null值
才删 行数据 -
thresh
:int型数据
表示非空值数,如果指定则覆盖how参数
,表示删除少于thresh个
非空值的行数据;默认为None
,表示根据how参数
删除 -
subset
:考虑非可空的列名列表,默认考虑全字段注意:小写的
null
才视为空,大写的NULL
不视为空删除存在null 的行 >>> df.dropna().show() +---+------+-----+ |age|height| name| +---+------+-----+ | 10| 80|Alice| +---+------+-----+ 删除全字段非空值少于3个的行 df.dropna(thresh=3).show() 删除指定字段存在空的行(若存在空值但指定字段不空,则不删) df.dropna(subset=["指定字段"])
-
-
fillna
替换null
值 别名为na.fill()
格式:fillna(value,subset = None )
value
:int,long,float,string或dict
。用于替换空值的值。若为dict型
,则字典的键必须为列名,字典的值为对应列名的替换值。且替换值必须是int,long,float,boolean或string
忽略subset 参数
subset
:要考虑的可选列名列表。默认全部字段都填充,但是若value
的数据类型与subset列表
中字段的数据类型不匹配则不填充>>> df4.na.fill(50).show() +---+------+-----+ |age|height| name| +---+------+-----+ | 10| 80|Alice| | 5| 50| Bob| | 50| 50| Tom| | 50| 50| null| +---+------+-----+ >>> df4.na.fill({'age': 50, 'name': 'unknown'}).show() +---+------+-------+ |age|height| name| +---+------+-------+ | 10| 80| Alice| | 5| null| Bob| | 50| null| Tom| | 50| null|unknown| +---+------+-------+
-
groupby
分组统计 根据指定列对进行分组,并可以对其进行汇总
格式:DataFlame.groupby(分组列).agg({'聚合列1':'聚合函数1','聚合列1':'聚合函数2'})
>>> df.select("name", "age").show() +--------+---+ | name|age| +--------+---+ | Ankit| 25| |Jalfaizy| 22| | saurabh| 20| | Bala| 26| +--------+---+
函数 含义 聚合函数 含义 聚合函数 含义 avg() 平均数 count() 个数 countDistinct() 不重复个数 first() 第一个值 sum() 累加和 sumDistinct() 不重复值累加和 max() 最大值 mean() 中位数 min() 最小值 stddev() 标准差 stddev_samp() - stddev_pop() 总体标准差 variance() 算数平方差 var_samp() - var_pop() 总体算数平方差 其中:当只有一行数据时,
stddev()
返回0, 而stddev_samp()
返回
null值 -
自定义的汇总方法
格式:DataFlame.agg(fn.聚合函数(指定列名).alias(列名), fn2...)
import pyspark.sql.functions as fn #调用函数并起一个别名 df.agg(fn.count('SepalWidth').alias('width_count'),fn.countDistinct('cls').alias('distinct_cls_count')).show()
-
自定义的函数(
udf
)
在RDD
中可以直接使用自定义函数,交给rdd
的transformatioins
方法进行执行
在DataFrame
中则需要通过udf
将自定义函数封装成udf
函数再交给DataFrame
进行调用执行from pyspark.sql.functions import udf def 自定义函数名(参数1,...): pass check = udf(自定义函数名,StringType()) # StringType 表示函数的返回值类型为字符串 resultDF = DF1.withColumn('New_cls',check(参数1,...))
-
randomSplit
拆分数据集,拆成两部分
格式 :df1,df2 = DataFlame.randomSplit([df1占比例, df2占比例])
trainDF, testDF = df.randomSplit([0.6, 0.4])
-
sample
采样数据(可用于绘制散点图)
格式:DataFlame.sample(抽样是否放回,采样比例,随机种子100)
sdf = df.sample(False,0.2,100)
-
subtract
查看两个数据集在类别上的差异
格式:DataFlame1.select(指定列).subtrac(DataFlame2.select(指定列))
商用场景:确保训练数据集覆盖了所有分类diff_in_train_test = testDF.select('cls').subtract(trainDF.select('cls')) diff_in_train_test.distinct().count()
-
join
数据集拼接df1 = df1.join(df2, 合并条件, 'outer') # 外连接+dropna 比 内连接 更快 df1 = df1.dropna()
-
crosstab
交叉表
格式:DataFrame.crosstab(指定列1, 指定列2)
- 指定列1的不同的项:为每行的首项数据(
spark dataframe 没有行索引
) - 指定列2的不同的项:为交叉表的 列名(第二项及其之后)
- 数据:为以列1做分组,统计列2不同项中 每项个数
df.crosstab('cls','SepalLength').show()
- 指定列1的不同的项:为每行的首项数据(
-
pivot
透视表
注意:若透视的字段中的不同属性值超过10000个,则需要设置spark.sql.pivotMaxValues
# 统计每个用户对各类商品的pv、fav、cart、buy数量 df = df.groupBy(df.userId, df.cateId).pivot("btag", ["pv","fav","cart","buy"]).count() df.printSchema() root |-- userId: integer (nullable = true) |-- cateId: integer (nullable = true) |-- pv: long (nullable = true) |-- fav: long (nullable = true) |-- cart: long (nullable = true) |-- buy: long (nullable = true)
-
corr
相关系数# 目前corr只能计算两列的pearson相关系数,比如 df.corr('balance', 'numTrans') # 而相关系数矩阵需要这么做 n_numerical = len(numerical) corr = [] for i in range(0, n_numerical): temp = [None] * i for j in range(i, n_numerical): temp.append(df3.corr(numerical[i], numerical[j])) corr.append(temp)
·
流式计算
Spark-Streaming(基于RDD)秒级
它是一个可扩展,高吞吐具有容错性的流式计算框架
吞吐量:单位时间内成功传输数据的数量
之前我们接触的spark-core
和spark-sql
都是处理属于离线批处理任务,数据一般都是在固定位置上,通常我们写好一个脚本,每天定时去处理数据,计算,保存数据结果。这类任务通常是T+1
(一天一个任务),对实时性要求不高
但也存在很多实时性处理的需求,例如:双十一的京东阿里,通常会做一个实时的数据大屏,显示实时订单。这种情况下,对数据实时性要求较高,仅仅能够容忍到延迟1分钟或几秒钟。
组件
Streaming Context
:Spark-Streaming
程序入口
- 一旦一个
Context
已经启动(调用Streaming Context.start()
),就不能有新的流算子(Dstream
)建立或者是添加到Context
中 - 一旦一个
Context
已经停止,不能重新启动(Streaming Context.stop()
方法后 就不能再次调start()
) - 在
JVM(java虚拟机)
中, 同一时间只能有一个Streaming Context
处于活跃状态, 一个SparkContext
创建一个Streaming Context
- 在
Streaming Context
上调用stop()
方法, 也会关闭SparkContext
对象, 如果只想仅关闭Streaming Context
对象,设置stop()
的可选参数为false
- 一个
SparkContext
对象可以重复利用去创建多个Streaming Context
对象(不关闭SparkContext
前提下), 但是需要关一个再开下一个
DStream
(离散流):Spark-Streaming
基本的数据抽象
- 代表一个连续的数据流
- 在内部,
DStream
由一系列连续的RDD
组成 DStream
中的每个RDD
都包含确定时间间隔内的数据- 任何对
DStreams
的操作都转换成了对DStreams
隐含的RDD
的操作
Receiver
:用于接收数据
数据源
- 基本源
TCP/IP Socket
FileSystem
- 高级源
Kafka
Flume
CheckPoint
:检查点
- 设置检查点目录
格式:streamingContext.checkpoint(检查点目录)
- 设置检查点的时间间隔
格式:dstream.checkpoint(时间间隔)
:设置checkpoint
间隔是DStream
的滑动间隔的5-10倍
DStream的操作
- 无状态转换(仅统计当前批次数据)
-
Transform
操作
-
RDD
操作
RDD
中提供了丰富的API
接口,Dstream
对RDD
的操作进行了封装,但封装并不完全,如果有些方法没有封装进去,但是还是需要使用这些方法,就可以调用transform
方法,相当于直接对RDD
进行操作 -
输出操作:类似于
RDD
中的action
,触发Dstream
的延迟操作
使用foreachRDD
将计算结果保存至mysql
-
from pyspark.streaming import StreamingContext
from pyspark import SparkContext
import pymysql
# 创建SparkContext
sc = sparkContext('local[2]','savemysql')
ssc = StreamingContext(sc, 3)
def printTemp(iter):
# 打开数据库连接
db = pymysql.connect("localhost", "root", "root", "word_count")
for rec in iter:
cursor = db.cursor()
sql = "INSERT INTO word(NAME,W_COUNT) VALUES (\"%s\",%s)"%(rec0,rec1)
try:
cursor.execute(sql)
db.commit()
except:
db.rollback()
db.close()
lines = ssc.socketTextStream("localhost", 9999)
counts = lines.flatMap(lambda line: line.split(" ")).map(lambda word: (word, 1)).reduceByKey(lambda x,y:x+y)
counts.foreachRDD(lambda rdd: rdd.foreachPartition(printTemp)) # 针对于每一个Partition,只做一个数据库连接
ssc.start()
ssc.awaitTermination()
- 有状态转换(会对之前的指定多个批次数据进行聚合操作)
-
滑动窗口转化操作
操作 含义 window(windowLength, slideInterval)
基于源DStream产生的窗口化的批数据,计算得到一个新的DStream countByWindow(windowLength, slideInterval)
返回流中元素的一个滑动窗口数 reduceByWindow(func, windowLength, slideInterval)
返回一个单元素流。利用函数 func
聚集滑动时间间隔的流的元素创建这个单元素流。函数func
必须满足结合律,从而可以支持并行计算reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks])
应用到一个 (K,V)
键值对组成的DStream
上时,会返回一个由(K,V)
键值对组成的新的DStream
。每一个key
的值均由给定的reduce函数(func)
进行聚合计算。注意:在默认情况下,这个算子利用了Spark默认的并发任务数去分组。可以通过numTasks参数的设置来指定不同的任务数reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks])
更加高效的 reduceByKeyAndWindow
,每个窗口的reduce
值,是基于先前窗口的reduce
值进行增量计算得到的;它会对进入滑动窗口的新数据进行reduce
操作,并对离开窗口的老数据进行逆向reduce
操作。但是,只能用于可逆reduce函数
,即那些reduce
函数都有一个对应的逆向reduce函数
countByValueAndWindow(windowLength, slideInterval, [numTasks])
当应用到一个 (K,V)
键值对组成的DStream
上,返回一个由(K,V)
键值对组成的新的DStream。每个key
的值都是它们在滑动窗口中出现的频率 -
updateStateByKey
操作
格式:updateStateByKey(updateFunc,numPartitions = None,initialRDD = None )
作用:返回一个新的“状态”DStream
,在该DStream中
,通过在键的先前状态和键的新值上应用给定的函数来更新每个键的状态。
参数:updateFunc
–状态更新功能。如果此函数返回None
,则将消除相应的状态键值对
步骤:
- 要定义一个state,可以是任意的数据类型
- 要定义state更新函数–指定一个函数如何使用之前的state和新值来更新state
- 对于每个batch,Spark都会为每个之前已经存在的key去应用一次state更新函数,无论这个key在batch中是否有新的数据。如果state更新函数返回none,那么key对应的state就会被删除
- 对于每个新出现的key,也会执行state更新函数
from pyspark.streaming import StreamingContext from pyspark.sql.session import SparkSession # 创建SparkContext spark = SparkSession.builder.master("local[2]").getOrCreate() sc = spark.sparkContext ssc = StreamingContext(sc, 3) #开启检查点 ssc.checkpoint("checkpoint") #定义state更新函数 def updateFunc(new_values, last_sum): return sum(new_values) + (last_sum or 0) lines = ssc.socketTextStream("localhost", 9999) # 对数据以空格进行拆分,分为多个单词 counts = lines.flatMap(lambda line: line.split(" ")) \ .map(lambda word: (word, 1)) \ #应用updateStateByKey函数 .updateStateByKey(updateFunc=updateFunc) counts.pprint() ssc.start() ssc.awaitTermination()
-
Spark Streaming编码步骤
- 创建一个
StreamingContext
- 根据不同的数据源 创建 数据对象(
Dstream
) - 对数据对象进行
Transformations
操作 - 输出结果(
Dstream.pprint()
) - 开始和停止(
StreamingContext.start()
和StreamingContext.stop()
)
from pyspark.streaming.kafka import KafkaUtils
# 步骤一
sc = sparkContext('local','test') # 创建sparkContext
ssc = StreamingContext(sc, 1) #创建StreamingContext。参数2:指定执行计算的时间间隔
# 步骤二(基于文件数据源)
lines = ssc.textFileStream('文件目录,file://表示本地文件,默认为 HADOOP文件') # 监听ip,端口上的上的数据
# 步骤二(基于 socket 数据源(仅用于测试))。在此处的socket是客户端,监听服务端传来的数据(nc -lk 9999 -v)
lines = ssc.socketTextStream('localhost',9999) # 监听ip,端口上的上的数据
# 步骤二(基于 kafka 数据源)。在此处的kafka是消费者,从指定topic消费数据
lines = KafkaUtils.createDirectStream(ssc,[topic],{"metadata.broker.list":'localhost:9092'})
# 步骤二(基于 Flume 数据源)push方式:spark被动获取数据 (Flume 的 sink 为 avro)
lines = FlumeUtils.createStream(ssc, "localhost",41414)
# 步骤二(基于 Flume 数据源)pull方式:spark主动获取数据 (Flume 的 sink 为 org.apache.spark.streaming.flume.sink.SparkSink)
lines = FlumeUtils.createPollingStream(ssc, "localhost",41414)
# 步骤三
wordCounts = lines.flatMap(lambda line: line.split(" ")).map(lambda word:(word,1))reduceByKey(lambda x,y:x+y)
#步骤四 打印结果信息,会执行前面的transformation操作执行
wordCounts.pprint()
# 步骤五。 启动StreamingContext 并 7x24 式运行,一般不执行stop关闭StreamingContext
ssc.start()
#等待计算结束
ssc.awaitTermination()
structured streaming(基于DataFrom)
关键思想:将实时数据流视为一张正在不断添加数据的,可从而将流计算等同于在一个静态表上的批处理查询,Spark会在不断添加数据的无界输入表上运行计算,并进行增量查询
两种处理模式(微批模式和持续模式)毫秒级
- 微批模式 (默认模式)百毫秒级
为保证端到端的一致性,会将 待处理数据的偏移量 同步写于日志中
- 持续处理模式(
spark 2.3及以上
)毫秒级
Spark
不再根据触发器来周期性启动任务,而是启动一系列的连续任务
待处理数据的偏移量 异步写于日志中,延迟更小,但仅能保证至少一次性,不能保证仅且只处理一次
structured streaming编码步骤
- 创建一个
StreamingSession
- 根据不同的数据源 创建 数据对象(
DataFrom
) - 定义流计算过程
- 启动流计算并输出结果
from pyspark.sql import SparkSession
from pyspark.sql.functions import explode, split
步骤1 创建SparkSession
spark = SparkSession.builder.appName("StructuredNetworkWordCount").getOrCreate()
spark.sparkContext.setLogLevel('WARN') # 设置日志输出级别
步骤2 基于 socket 数据流 创建
lines =spark.readStream.format("socket").option("host","localhost") .option("port", 9999).load()
步骤2 基于 rote 数据流 创建 Rate源可每秒生成特定个数的数据行,每个数据行包括时间截和行值字段
lines = spark.readStream.format("rate").option('rowsPerSecond', 5) .load() # 每秒生成5行 仅用于测试
步骤2 基于 json文件 数据流 创建
lines = spark.readStream.format("json").schema(schema).option("maxFilesPerTrigger", 100).load('json文件路径') # 每次最多读100个json文件
步骤2 基于 Kafka 数据流 创建
lines = spark.readStream.format("kafka").option("kafka.bootstrap.servers", "localhost:9092").option("subscribe", 'wordcount-topic').load()
.selectExpr("CAST(value AS STRING)") # 将从kafka拿到的字节数组类型数据转成字符串类型
步骤3 可以使用 DataFrom算子 和 pyspark.sql.functions中的内置函数
words = lines.select( explode( split(lines.value,).alias("word")
wordCounts =words.groupBy("word").counto
步骤3 使用窗口统计
windowDuration ='1 minutes' # 定义窗口
windowedCounts =line.filter("action ='purchase'") # 过滤出购买的记录
.groupBy('district', window('eventTime', windowDuration)) # 根据分区将一分钟内的窗口数据进行分区分组
.count() # 统计次数
.sort(asc('window')) # 根据窗口进行升序排序
步骤4 开始流计算并输出结果
query = wordCounts
[.selectExpr("CAST(value AS STRING) as key", "CONCAT(CAST(value AS STRING),':', CAST(count AS STRING)) as value") # 将数据进行转换 ] 可选
.writeStream
.outputMode("complete") # 定义输出模式
.format("console") # 结果输出控制台 仅用于测试
[.format("kafka").option("kafka.bootstrap.servers", "localhost:9092").option("topic"". "wordcount-result-topic") # 将结果输出到 kafka 中]
[.format("parquet").option("path", "file:///tmp/文件路径") # 将结果输出到 parquet文件 中]
.option("checkpointLocation", "file://tmp/kafika-sp") #设置检查点路径
.option("truncate', "false") # 某行输出过长是否截断
.trigger(processingTime="8 seconds") # 触发流计算的时间间隔
.start()
1. format: 接收器类型
2. outputMode:输出模式,指定写入接收器的内容,可以是Append模式、Complete模式或Update模式"
Append模式:只有结果表中自上次触发间隔后增加的新行,才会被写入外部存储器。这种模式般适用于“不希望更改结果表中现有行的内容”的使用场景。
Complete模式:已更新的完整的结果表可被写入外部存储器.
Update模式:只有自上次触发间隔后结果表中发生更新的行,才会被写入外部存储器。这种模式与Complete模式相比,输出较少,如果结果表的部分行没有更新,则不会输出任何内容.当查询不包括聚合时,这个機式等同于Append模式
4. queryName:查询的名称,可选,用于标识查询的唯一名称
5. trigger:触发间隔,可选,设定触发间隔,如果未指定,则系统将在上一次处理完成后立即检查新数据的可用性,如果由于先前的处理尚未完成导致超过触发间隔,则系统将在处理完成后立即触发新的查询
query.awaitTermination()