Spark学习笔记
前言:今天是温习 Spark 的第 2 天啦!主要梳理了 Spark 核心数据结构:RDD(弹性分布式数据集),其中包括基于内存计算的 SparkCore 各类技术知识点希望对大家有帮助!
Tips:"分享是快乐的源泉💧,在我的博客里,不仅有知识的海洋🌊,还有满满的正能量加持💪,快来和我一起分享这份快乐吧😊!
喜欢我的博客的话,记得点个红心❤️和小关小注哦!您的支持是我创作的动力!"
4. RDD简介
(本节的所有数据集放在我的资源下载区哦,感兴趣的小伙伴可以自行下载:
最全面的SparkCore系列案例数据集
)
(1) RDD五大属性总结
- 1-分区列表:RDD是由一些列分区组成的
- 2-计算函数
- 3-依赖关系:比如reduceByKey依赖于map依赖于flatMap
- 4-key-value的分区器:默认分区是hash分区,可以变更为range分区等
- 5-位置优先性: 按照"移动数据不如移动计算"的理念,Spark在进行任务调度的时候,会尽可能选择那些存有数据的worker节点来进行任务计算。(数据本地性)
#### (2) RDD的2种创建方法
- 1-使用并行化集合,本质上就是将本地集合作为参数传递到sc.pa
- 2-使用sc.textFile方式读取外部文件系统,包括hdfs和本地文件系统
_01_createRDD.py
# -*- coding: utf-8 -*-
# Program function:创建RDD的两种方式
'''
第一种方式:使用并行化集合,本质上就是将本地集合作为参数传递到sc.pa
第二种方式:使用sc.textFile方式读取外部文件系统,包括hdfs和本地文件系统
1-准备SparkContext的入口,申请资源
2-使用rdd创建的第一种方法
3-使用rdd创建的第二种方法
4-关闭SparkContext
'''
from pyspark import SparkContext,SparkConf
if __name__ == "__main__":
print("===================createRDD==========================")
# 1. 准备spark上下文环境
conf = SparkConf().setAppName("CreateRDD").setMaster("local[5]")
sc = SparkContext(conf=conf)
sc.setLogLevel("WARN")
# 2.使用rdd创建的第一种方法
connection_rdd = sc.parallelize([1,2,3,4,5])
print(connection_rdd.collect())
# 3.如何使用api获取rdd的分区个数
print(connection_rdd.getNumPartitions())
# 4.使用rdd创建的第二种方法
file_rdd = sc.textFile("D:\PythonProject\Bigdata_Pyspark3.1.2\PySpark-SparkCore_3.1.2\data\words.txt")
print(file_rdd.collect())
print(file_rdd.getNumPartitions())
# 5. 关闭SparkContext
sc.stop()
(3)读取外部数据的两种方式
- 1-sc.textFile:读取外部文件系统,包括hdfs和本地文件系统,可以分区
- 2-sc.wholeTextFiles:可以读取文件夹下面的所有小文件,可以分区
- 读取分区数据:file_rdd.glom().collect()
- 不排序,获取第一个数据:take(1)
- 默认降序排序,获取第一个数据:top(1)
- 默认升序排序,获取第一个数据:takeOrdered(1)
- 3-并行度分区总结
- 0.local[]不指定时,那么默认就是 4,且 minparallelism 最小值是 2
- 1.setMaster(local[5]),如果不额外指定,那么getNumPartitions 就是5
- 2.setMaster(local[5],而parallelize([], 3)),那么getNumPartitions 就是3
- 3.wholeTextFile(,3), 那么getNumPartitions 就是 3
- 4.如果sc.textFile读取的是文件夹中多个文件,这里的分区个数是以文件个数为主的,自己写的分区不起作用
_02_createWholeTextFile.py
# -*- coding: utf-8 -*-
# Program function:创建RDD的两种方式
'''
第一种方式:使用并行化集合,本质上就是将本地集合作为参数传递到sc.pa
第二种方式:使用sc.textFile方式读取外部文件系统,包括hdfs和本地文件系统
1-准备SparkContext的入口,申请资源
2-使用rdd创建的第一种方法
3-使用rdd创建的第二种方法
4-关闭SparkContext
'''
from pyspark import SparkConf, SparkContext
if __name__ == '__main__':
print("=========createRDD==============")
# 1 - 准备SparkContext的入口,申请资源
conf = SparkConf().setAppName("createRDD").setMaster("spark://node1:7077")
sc = SparkContext(conf=conf)
# 3 - 使用rdd创建的第二种方法
# minPartitions最小的分区个数,最终有多少的分区个数,以实际打印为主
file_rdd = sc.parallelize([1, 2, 3, 4, 5], 6) # 分区数 > 元素 ,会有一个分区没有数据
print("rdd numpatitions:{}".format(file_rdd.getNumPartitions()))
## 如何打印每个分区的内容
print("per partition content:", file_rdd.glom().collect())
# wholeTextFiles 读取小文件
file_rdd = sc.wholeTextFiles("hdfs://node1:9820/pydata/input/rdddata/ratings100", 3)
print("rdd numpartitions:{}".format(file_rdd.getNumPartitions()))
# print(" file_rdd per partition content:",file_rdd.glom().collect()) # 映射
# 如果sc.textFile读取的是文件夹中多个文件,这里的分区个数是以文件个数为主的,自己写的分区不起作用
# file_rdd = sc.textFile("/export/data/pyspark_workspace/PySpark-SparkCore_3.1.2/data/ratings100", 3)
file_rdd = sc.textFile("hdfs://node1:9820/pydata/input/rdddata/ratings100")
print("rdd numpartitions:{}".format(file_rdd.getNumPartitions()))
print(file_rdd.take(1)) # take用于获取RDD中从0到num-1下标的元素,不排序。
print(file_rdd.top(1)) # top函数用于从RDD中,按照默认(降序)或者指定的排序规则,返回前num个元素。
print(file_rdd.takeOrdered(1)) # 与top相反,取了最小值,默认升序
# 4 - 关闭SparkContext
print("成功!")
sc.stop()
(4) Transformer算子
- 1- 单value类型
- map操作
- filter操作
- flatmap操作
- groupBy操作
- mapValue操作
_01_singleValueOperation
# -*- coding: utf-8 -*-
# Program function:完成单Value类型RDD的转换算子的演示
from pyspark import SparkContext, SparkConf
import re
'''
分区内:一个rdd可以分为很多分区,每个分区里面都是有大量元素,每个分区都需要线程执行
分区间:有一些操作分区间做一些累加
'''
if __name__ == "__main__":
# 1-创建SparkContext申请资源
conf = SparkConf().setAppName("mini").setMaster("local[*]")
sc = SparkContext(conf=conf)
sc.setLogLevel("WARN")
# 2-map操作
print("==========================map操作=================================")
rdd1 = sc.parallelize([1, 2, 3, 4, 5])
print("rdd1 numpartitions:{}".format(rdd1.getNumPartitions()))
rdd_map = rdd1.map(lambda x: x * 2)
print("rdd_map numpartitions:{}".format(rdd_map.getNumPartitions()))
print(rdd_map.glom().collect())
# 3-filter操作
print("==========================filter操作=================================")
print(rdd1.collect())
print(rdd1.glom().collect())
print(rdd1.filter(lambda x: x > 3).glom().collect())
# 4-flatmap操作
print("==========================flatmap操作=================================")
rdd2 = sc.parallelize([" hello you", "hello me "])
# 匹配一个或多个空白字符,包括空格、制表符和换行符
print(rdd2.flatMap(lambda word: re.split("\s+", word.strip())).collect())
# 5-groupBy操作
print("==========================groupBy操作=================================")
x = sc.parallelize([1, 2, 3])
y = x.groupBy(lambda x: 'A' if (x % 2 == 1) else 'B')
print(y.mapValues(list).collect())
# 6-mapValue操作
print("==========================mapValue操作=================================")
x1 = sc.parallelize([("a", ["apple", "banana", "lemon"]), ("b", ["grapes"])])
def f(x):
return len(x)
print(x1.mapValues(f).collect())
==========================map操作=================================
rdd1 numpartitions:4
rdd_map numpartitions:4
[[2], [4], [6], [8, 10]]
==========================filter操作=================================
[1, 2, 3, 4, 5]
[[1], [2], [3], [4, 5]]
[[], [], [], [4, 5]]
==========================flatmap操作=================================
['hello', 'you', 'hello', 'me']
==========================groupBy操作=================================
[('A', [1, 3]), ('B', [2])]
==========================mapValue操作=================================
[('a', 3), ('b', 1)]
- 2-双value类型
- 并集:A.union(B)
- 交集:A.intersection(B)
- 差集:大.subtract(小)
- 去重:distinct()
_02_doubleValuesOperation.py
# -*- coding: utf-8 -*-
# Program function:完成单Value类型RDD的转换算子的演示
from pyspark import SparkConf, SparkContext
import re
'''
分区内:一个rdd可以分为很多分区,每个分区里面都是有大量元素,每个分区都需要线程执行
分区间:有一些操作分区间做一些累加
'''
if __name__ == '__main__':
# 1-创建SparkContext申请资源
conf = SparkConf().setAppName("mini2").setMaster("local[*]")
sc = SparkContext.getOrCreate(conf=conf)
sc.setLogLevel("WARN") # 一般在工作中不这么写,直接复制log4j文件
# 2-对两个RDD求并集
print("========================并集=============================")
rdd1 = sc.parallelize([1, 2, 3, 4, 5])
rdd2 = sc.parallelize([1, 2, 3, 4, 5, 6, 7, 8])
Union_RDD = rdd1.union(rdd2)
print(Union_RDD.collect())
# 3-对两个rdd求交集
print("========================交集=============================")
print(rdd1.intersection(rdd2).collect())
# 4-对两个rdd求差集
print("========================差集=============================")
print(rdd2.subtract(rdd1).collect())
# Return a new RDD containing the distinct elements in this RDD.
print(Union_RDD.distinct().collect())
print(Union_RDD.distinct().glom().collect())
========================并集=============================
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6, 7, 8]
========================交集=============================
[1, 2, 3, 4, 5]
========================差集=============================
[8, 6, 7]
[8, 1, 2, 3, 4, 5, 6, 7]
[[8], [1], [2], [3], [4], [5], [6], [7]] ================每个rdd四个分区,两个rdd八个分区
- 3-key-Value算子
- groupByKey:按照key进行分组有地址,用mapValue获取值
- reduceByKey:键一起,值聚合
- sortByKey:按照键排序
- countByKey:计数
- combineByKey是底层API
- foldBykey
- aggreateBykey
_03_keyValuesOperation.py
# -*- coding: utf-8 -*-
# Program function:完成单Value类型RDD的转换算子的演示
from pyspark import SparkConf, SparkContext
import re
'''
分区内:一个rdd可以分为很多分区,每个分区里面都是有大量元素,每个分区都需要线程执行
分区间:有一些操作分区间做一些累加
'''
if __name__ == '__main__':
# 1-创建SparkContext申请资源
conf = SparkConf().setAppName("mini2").setMaster("local[*]")
sc = SparkContext.getOrCreate(conf=conf)
sc.setLogLevel("WARN") # 一般在工作中不这么写,直接复制log4j文件
# 2-key和value类型算子
# groupByKey
rdd1 = sc.parallelize([("a", 1), ("b", 2)])
rdd2 = sc.parallelize([("c", 1), ("b", 3)])
rdd3 = rdd1.union(rdd2)
key1 = rdd3.groupByKey()
print("=============================groupByKey====================================")
print("groupByKey:", key1.collect())
print(key1.mapValues(list).collect()) # 需要通过mapValue获取groupByKey的值
print(key1.mapValues(tuple).collect())
print("=============================reduceByKey====================================")
# reduceByKey
key2 = rdd3.reduceByKey(lambda x, y: x + y)
print(key2.collect())
print("=============================sortByKey====================================")
# sortByKey
print(key2.map(lambda x: (x[1], x[0])).sortByKey(False).collect())
# [(5, 'b'), (1, 'c'), (1, 'a')]
print("=============================countByKey====================================")
# countByKey
print(rdd3.countByKey())
=============================groupByKey====================================
groupByKey: [
('a', <pyspark.resultiterable.ResultIterable object at 0x7f96e0ae34c0>),
('b', <pyspark.resultiterable.ResultIterable object at 0x7f96e0aaad90>),
('c', <pyspark.resultiterable.ResultIterable object at 0x7f96e0aaadc0>)]
[('a', [1]), ('b', [2, 3]), ('c', [1])]
[('a', (1,)), ('b', (2, 3)), ('c', (1,))]
=============================reduceByKey====================================
[('a', 1), ('b', 5), ('c', 1)]
=============================sortByKey====================================
[(5, 'b'), (1, 'a'), (1, 'c')]
=============================countByKey====================================
defaultdict(<class 'int'>, {'a': 1, 'b': 2, 'c': 1})
(5)Action算子
- 1-主要算子
- first():第一个元素
- reduce(add):元素累加
- reduce(mul):累乘
- takeSample:随机数
_04_actionOperation.py
# -*- coding: utf-8 -*-
# Program function:完成单Value类型RDD的转换算子的演示
from pyspark import SparkConf, SparkContext
import re
'''
分区内:一个rdd可以分为很多分区,每个分区里面都是有大量元素,每个分区都需要线程执行
分区间:有一些操作分区间做一些累加
'''
if __name__ == '__main__':
# 1-创建SparkContext申请资源
conf = SparkConf().setAppName("mini2").setMaster("local[*]")
sc = SparkContext.getOrCreate(conf=conf)
sc.setLogLevel("WARN") # 一般在工作中不这么写,直接复制log4j文件
# 2-key和value类型算子
# groupByKey
rdd1 = sc.parallelize([("a", 1), ("b", 2)])
rdd2 = sc.parallelize([("c", 1), ("b", 3)])
print(rdd1.first())
print(rdd1.take(2))
print(rdd1.top(2))
print(rdd1.collect())
rdd3 = sc.parallelize([1, 2, 3, 4, 5])
from operator import add
from operator import mul
print(rdd3.reduce(add))
print(rdd3.reduce(mul))
rdd4 = sc.parallelize(range(0, 10))
# 能否保证每次抽样结果是一致的,使用seed随机数种子
print(rdd4.takeSample(True, 3, 123))
print(rdd4.takeSample(True, 3, 123))
print(rdd4.takeSample(True, 3, 123))
print(rdd4.takeSample(True, 3, 34))
('a', 1)
[('a', 1), ('b', 2)]
[('b', 2), ('a', 1)]
[('a', 1), ('b', 2)]
15
120
[4, 7, 2]
[4, 7, 2]
[4, 7, 2]
[9, 9, 4]
- 2-其他算子
- foreach:Applies a function to all elements of this RDD.
- foreachPartition:Applies a function to each partition of this RDD.
- map:按照元素进行转换
- mapPartiton:按照分区进行转换
_05_otherOperation.py
# -*- coding: utf-8 -*-
# Program function:完成单Value类型RDD的转换算子的演示
from pyspark import SparkConf, SparkContext
import re
'''
分区内:一个rdd可以分为很多分区,每个分区里面都是有大量元素,每个分区都需要线程执行
分区间:有一些操作分区间做一些累加
'''
def f(iterator): # 【1,2,3】 【4,5】
for x in iterator: # for x in 【1,2,3】 x=1,2,3 print 1.2.3
print(x)
def f1(iterator): # 【1,2,3】 【4,5】 sum(1+2+3) sum(4+5)
yield sum(iterator)
if __name__ == '__main__':
# 1-创建SparkContext申请资源
conf = SparkConf().setAppName("mini2").setMaster("local[*]")
sc = SparkContext.getOrCreate(conf=conf)
sc.setLogLevel("WARN") # 一般在工作中不这么写,直接复制log4j文件
# 2-foreach-Applies a function to all elements of this RDD.
rdd1 = sc.parallelize([("a", 1), ("b", 2)])
print(rdd1.glom().collect())
# def f(x):print(x)
rdd1.foreach(lambda x: print(x))
# 3-foreachPartition--Applies a function to each partition of this RDD.
# 从性能角度分析,按照分区并行比元素更加高效
rdd1.foreachPartition(f)
# 4-map---按照元素进行转换
rdd2 = sc.parallelize([1, 2, 3, 4])
print(rdd2.map(lambda x: x * 2).collect())
# 5-mapPartiton-----按照分区进行转换
# Return a new RDD by applying a function to each partition of this RDD.
print(rdd2.mapPartitions(f1).collect()) # [3, 7]
[[], [('a', 1)], [], [('b', 2)]]
('a', 1)
('b', 2)
('b', 2)
('a', 1)
[2, 4, 6, 8]
[1, 2, 3, 4]
(6)重要函数
-
1-重分区函数
repartition:默认调用的是coalese的shuffle为True的方法(会产生分区数据为空)
coalese:减少分区函数
PartitonBy:可以调整分区,还可以调整分区器(一种hash分区器(一般打散数据)
_06_repartitionOperation.py
# -*- coding: utf-8 -*-
# Program function:完成单Value类型RDD的转换算子的演示
from pyspark import SparkConf, SparkContext
import re
'''
分区内:一个rdd可以分为很多分区,每个分区里面都是有大量元素,每个分区都需要线程执行
分区间:有一些操作分区间做一些累加
alt+6 可以调出来所有TODO,
TODO是Python提供了预留功能的地方
'''
if __name__ == '__main__':
# TODO: 1-创建SparkContext申请资源
conf = SparkConf().setAppName("mini2").setMaster("local[*]")
sc = SparkContext.getOrCreate(conf=conf)
sc.setLogLevel("WARN") # 一般在工作中不这么写,直接复制log4j文件
# TODO: 2-执行重分区函数--repartition
rdd1 = sc.parallelize([1, 2, 3, 4, 5, 6], 3)
print("partitions num:", rdd1.getNumPartitions())
print(rdd1.glom().collect()) # [[1, 2], [3, 4], [5, 6]]
print("repartition result:")
# TODO: repartition可以增加分区也可以减少分区,但是都会产生shuflle,如果减少分区的化建议使用coalesc避免发生shuffle
rdd__repartition1 = rdd1.repartition(5)
print("increase partition", rdd__repartition1.glom().collect()) # [[], [1, 2], [5, 6], [3, 4], []]
rdd__repartition2 = rdd1.repartition(2)
print("decrease partition", rdd__repartition2.glom().collect()) # decrease partition [[1, 2, 5, 6], [3, 4]]
# TODO: 3-减少分区--coalese
print(rdd1.coalesce(2).glom().collect()) # [[1, 2], [3, 4, 5, 6]]
print(rdd1.coalesce(5).glom().collect()) # [[1, 2], [3, 4], [5, 6]]
print(rdd1.coalesce(5, True).glom().collect()) # [[], [1, 2], [5, 6], [3, 4], []]
# 结论:repartition默认调用的是coalese的shuffle为True的方法
# TODO: 4-PartitonBy,可以调整分区,还可以调整分区器(一种hash分区器(一般打散数据),一种range分区器(排序拍好的))
# 此类专门针对RDD中数据类型为KeyValue对提供函数
# rdd五大特性中有第四个特点key-value分区器,默认是hashpartitioner分区器
rdd__map = rdd1.map(lambda x: (x, x))
print("partitions length:", rdd__map.getNumPartitions()) # partitions length: 3
print(rdd__map.partitionBy(2).glom().collect())
ssh://root@192.168.52.3:22/root/anaconda3/envs/pyspark_env/bin/python3 -u /export/data/spark_practice/PySpark-SparkCore_3.1.2/main/rddOperation/_06_repartitionOperation.py
23/12/15 17:11:55 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Using Spark's default log4j profile: org/apache/spark/log4j-defaults.properties
Setting default log level to "WARN".
To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).
partitions num: 3
[[1, 2], [3, 4], [5, 6]]
repartition result:
increase partition [[], [1, 2], [5, 6], [3, 4], []]
decrease partition [[1, 2, 5, 6], [3, 4]]
[[1, 2], [3, 4, 5, 6]]
[[1, 2], [3, 4], [5, 6]]
[[], [1, 2], [5, 6], [3, 4], []]
partitions length: 3
[[(2, 2), (4, 4), (6, 6)], [(1, 1), (3, 3), (5, 5)]]
- byKey类的聚合函数(一)
- fold:聚合计算
- aggregate:fold是aggregate的简化版本,fold分区内和分区间的函数是一致的
_07_ByKeyOperation.py
# -*- coding: utf-8 -*-
# Program function:完成单Value类型RDD的转换算子的演示
from pyspark import SparkConf, SparkContext
import re
'''
分区内:一个rdd可以分为很多分区,每个分区里面都是有大量元素,每个分区都需要线程执行
分区间:有一些操作分区间做一些累加
alt+6 可以调出来所有TODO,
TODO是Python提供了预留功能的地方
'''
def addNum(x,y):
return x+y
if __name__ == '__main__':
# TODO: 1-创建SparkContext申请资源
conf = SparkConf().setAppName("mini2").setMaster("local[*]")
sc = SparkContext.getOrCreate(conf=conf)
sc.setLogLevel("WARN") # 一般在工作中不这么写,直接复制log4j文件
# TODO: 2-使用reduce进行聚合计算
rdd1 = sc.parallelize([1, 2, 3, 4, 5, 6], 3)
from operator import add
# 直接得到返回值-21
print(rdd1.reduce(add))
# TODO: 3-使用fold进行聚合计算
# 第一个参数zeroValue是初始值,会参与分区的计算
# 第二个参数是执行运算的operation
print(rdd1.fold(0, add)) # 21
print(rdd1.getNumPartitions()) # 3
print(rdd1.glom().collect())
print("fold result:", rdd1.fold(10, add))
# TODO: 3-使用aggreate进行聚合计算
# seqOp分区内的操作, combOp分区间的操作
print(rdd1.aggregate(0, add, add)) # 21
print(rdd1.glom().collect())
print("aggregate result:", rdd1.aggregate(1, add, add)) # aggregate result: 25
# 结论:fold是aggregate的简化版本,fold分区内和分区间的函数是一致的
print("aggregate result:", rdd1.aggregate(1, addNum, addNum)) # aggregate result: 25
21
21
3
[[1, 2], [3, 4], [5, 6]]
fold result: 61
21
[[1, 2], [3, 4], [5, 6]]
aggregate result: 25
aggregate result: 25
- byKey类的聚合函数(二)
- groupByKey:会返回一个地址信息,需要mapValues(list)/(sum)取值
- reduceByKey:聚合操作
- foldByKey:有初始值,函数
- aggregateByKey:有初始值,seqOp分区内的操作,combOp分区间的操作
_08_ByKeyOperation.py
# -*- coding: utf-8 -*-
# Program function:完成单Value类型RDD的转换算子的演示
from pyspark import SparkConf, SparkContext
import re
'''
分区内:一个rdd可以分为很多分区,每个分区里面都是有大量元素,每个分区都需要线程执行
分区间:有一些操作分区间做一些累加
alt+6 可以调出来所有TODO,
TODO是Python提供了预留功能的地方
'''
def addNum(x, y):
return x + y
if __name__ == '__main__':
# TODO: 1-创建SparkContext申请资源
conf = SparkConf().setAppName("mini2").setMaster("local[*]")
sc = SparkContext.getOrCreate(conf=conf)
sc.setLogLevel("WARN") # 一般在工作中不这么写,直接复制log4j文件
# TODO: 2-使用groupByKey进行聚合计算
from operator import add
rdd = sc.parallelize([("a", 1), ("b", 1), ("a", 1)])
# groupByKey官网建议如果操作聚合功能建议使用reduceBykey和combineByKey
group_by_key_rdd = rdd.groupByKey()
print(group_by_key_rdd.collect()) # [('b', <pyspark.resultiterable.ResultIterable object at 0x7ff7a6e21970>),
print("groupBy value is:",
sorted(group_by_key_rdd.mapValues(list).collect())) # groupBy value is: [('a', [1, 1]), ('b', [1])]
# 如何实现基于相同key的value的累加操作,实现wordcount的操作
print("count value is:", sorted(group_by_key_rdd.mapValues(sum).collect())) # count value is: [('a', 2), ('b', 1)]
# TODO: 2-使用reduceByKey进行聚合计算,redueByKey提前会实现预聚合功能,性能更好
reduce_by_key_rdd = rdd.reduceByKey(lambda x, y: x + y)
print(reduce_by_key_rdd.collect())
# TODO: 3-使用foldByKey算子进行聚合操作
fold_by_key_rdd = rdd.foldByKey(0, add)
print("fold_by_key_rdd:", fold_by_key_rdd.collect())
# 这里初始值给1,小测试
print(rdd.getNumPartitions())
print("rdd glom result:", rdd.glom().collect()) # rdd glom result: [[('a', 1)], [('b', 1), ('a', 1)]]
fold_by_key_rdd1 = rdd.foldByKey(1, add)
print(fold_by_key_rdd1.collect()) # [('b', 2), ('a', 4)]
# TODO: 4-使用aggreateByKey算子进行聚合操作,这里也是类似于fold和aggreate,foldByKey是简化版本的aggreateByKey,
# foldByKey的分区内和分区间的函数是一致的
aggregate_by_key_rdd = rdd.aggregateByKey(0, add, add)
print("aggregate_by_key_rdd", aggregate_by_key_rdd.collect()) # aggregate_by_key_rdd [('b', 1), ('a', 2)]
aggregate_by_key_rdd1 = rdd.aggregateByKey(1, add, add)
print(aggregate_by_key_rdd1.collect()) # [('b', 2), ('a', 4)]
(7) WordCount的处理方法
- 1-数据处理:filter过滤-flatMap扁平化-map映射格式
- 2-reduceByKey进行聚合计算
- 3-groupByKey进行聚合计算,记得mapValues(sum)
- 4-foldByKey算子进行聚合操作
- 5-aggreateByKey算子进行聚合操作
_09_wordcountWay.py
# -*- coding: utf-8 -*-
# Program function:完成单Value类型RDD的转换算子的演示
from pyspark import SparkConf, SparkContext
import re
'''
分区内:一个rdd可以分为很多分区,每个分区里面都是有大量元素,每个分区都需要线程执行
分区间:有一些操作分区间做一些累加
alt+6 可以调出来所有TODO,
TODO是Python提供了预留功能的地方
'''
def addNum(x, y):
return x + y
if __name__ == '__main__':
# TODO: 1-创建SparkContext申请资源
conf = SparkConf().setAppName("mini2").setMaster("local[*]")
sc = SparkContext.getOrCreate(conf=conf)
sc.setLogLevel("WARN") # 一般在工作中不这么写,直接复制log4j文件
# TODO: 2-基础数据处理
from operator import add
file_rdd = sc.textFile("/export/data/spark_practice/PySpark-SparkCore_3.1.2/data/words.txt")
map_rdd = file_rdd \
.filter(lambda line: len(line.strip()) > 0) \
.flatMap(lambda line: re.split("\s+", line)) \
.map(lambda word: (word, 1))
# print(map_rdd.collect())
# [('hello', 1), ('you', 1), ('Spark', 1), ('Flink', 1), ('hello', 1), ('me', 1), ('hello', 1), ('she', 1), ('Spark', 1)]
# TODO: 3-使用reduceByKey进行聚合计算,redueByKey提前会实现预聚合功能,性能更好
result1 = map_rdd \
.reduceByKey(lambda x, y: x + y) \
.collect()
print("result1 reduceByKey opration is:", result1)
# TODO: 2-使用groupByKey进行聚合计算
result2 = map_rdd \
.groupByKey() \
.mapValues(sum) \
.collect()
print("result2 reduceByKey opration is:", result2)
# TODO: 4-使用foldByKey算子进行聚合操作
result3 = map_rdd \
.foldByKey(0, add) \
.collect()
print("result3 reduceByKey opration is:", result3)
# TODO: 5-使用aggreateByKey算子进行聚合操作,这里也是类似于fold和aggreate,foldByKey是简化版本的aggreateByKey,
# foldByKey的分区内和分区间的函数是一致的
result4 = map_rdd \
.aggregateByKey(0, add, add) \
.collect()
print("result4 reduceByKey opration is:", result4)
result1 reduceByKey opration is: [('Spark', 2), ('Flink', 1), ('hello', 3), ('you', 1), ('me', 1), ('she', 1)]
result2 reduceByKey opration is: [('Spark', 2), ('Flink', 1), ('hello', 3), ('you', 1), ('me', 1), ('she', 1)]
result3 reduceByKey opration is: [('Spark', 2), ('Flink', 1), ('hello', 3), ('you', 1), ('me', 1), ('she', 1)]
result4 reduceByKey opration is: [('Spark', 2), ('Flink', 1), ('hello', 3), ('you', 1), ('me', 1), ('she', 1)]
(8) CombineByKey的两种方法
- 1-combineByKey(createCombiner, mergeValue, mergeCombiners)-汇聚类
_10_combineByKeyWay
# -*- coding: utf-8 -*-
# Program function:完成单Value类型RDD的转换算子的演示
from pyspark import SparkConf, SparkContext
import re
'''
分区内:一个rdd可以分为很多分区,每个分区里面都是有大量元素,每个分区都需要线程执行
分区间:有一些操作分区间做一些累加
alt+6 可以调出来所有TODO,
TODO是Python提供了预留功能的地方
'''
'''
对初始值进行操作
'''
def createCombiner(value): #('a',[1])
return [value]
# 这里的x=createCombiner得到的[value]结果
def mergeValue(x,y): #这里相同a的value=y=1
x.append(y)#('a', [1, 1]),('b', [1])
return x
def mergeCombiners(a,b):
a.extend(b)
return a
if __name__ == '__main__':
# TODO: 1-创建SparkContext申请资源
conf = SparkConf().setAppName("mini2").setMaster("local[*]")
sc = SparkContext.getOrCreate(conf=conf)
sc.setLogLevel("WARN") # 一般在工作中不这么写,直接复制log4j文件
# TODO: 2-基础数据处理
from operator import add
rdd = sc.parallelize([("a", 1), ("b", 1), ("a", 1)])
# [(a:[1,1]),(b,[1,1])]
print(sorted(rdd.groupByKey().mapValues(list).collect()))
# 使用自定义集聚合函数组合每个键的元素的通用功能。
# - `createCombiner`, which turns a V into a C (e.g., creates a one-element list)
# 对初始值进行操作
# - `mergeValue`, to merge a V into a C (e.g., adds it to the end ofa list)
# 对分区内的元素进行合并
# - `mergeCombiners`, to combine two C's into a single one (e.g., merges the lists)
# 对分区间的元素进行合并
by_key_result = rdd.combineByKey(createCombiner, mergeValue, mergeCombiners)
print(sorted(by_key_result.collect()))#[('a', [1, 1]), ('b', [1])]
[('a', [1, 1]), ('b', [1])]
[('a', [1, 1]), ('b', [1])]
- 2-求平均成绩
_11_combineByKeyWay2.py
# -*- coding: utf-8 -*-
# Program function:完成单Value类型RDD的转换算子的演示
from pyspark import SparkConf, SparkContext
import re
'''
分区内:一个rdd可以分为很多分区,每个分区里面都是有大量元素,每个分区都需要线程执行
分区间:有一些操作分区间做一些累加
alt+6 可以调出来所有TODO,
TODO是Python提供了预留功能的地方
'''
'''
对初始值进行操作
[value,1],value指的是当前学生成绩,1代表的是未来算一下一个学生考了几次考试
("Fred", 88)---------->[88,1]
'''
def createCombiner(value): #
return [value, 1]
'''
x代表的是 [value,1]值,x=[88,1]
y代表的相同key的value,比如("Fred", 95)的95,执行分区内的累加
'''
def mergeValue(x, y):
return [x[0] + y, x[1] + 1]
'''
a = a[0] value,a[1] 几次考试
上一步结果(160,2)
这一步(91,1)
=160+91,2+1
'''
def mergeCombiners(a, b):
return [a[0] + b[0], a[1] + b[1]]
if __name__ == '__main__':
# TODO: 1-创建SparkContext申请资源
conf = SparkConf().setAppName("mini2").setMaster("local[*]")
sc = SparkContext.getOrCreate(conf=conf)
sc.setLogLevel("WARN") # 一般在工作中不这么写,直接复制log4j文件
# TODO: 2-基础数据处理
from operator import add
# 这里需要实现需求:求解一个学生的平均成绩
x = sc.parallelize([("Fred", 88), ("Fred", 95), ("Fred", 91), ("Wilma", 93), ("Wilma", 95), ("Wilma", 98)], 3)
print(x.glom().collect())
# 第一个分区("Fred", 88), ("Fred", 95)
# 第二个分区("Fred", 91), ("Wilma", 93),
# 第三个分区("Wilma", 95), ("Wilma", 98)
# reduceByKey
reduce_by_key_rdd = x.reduceByKey(lambda x, y: x + y)
print("reduceBykey:", reduce_by_key_rdd.collect()) # [('Fred', 274), ('Wilma', 286)]
# 如何求解平均成绩?
# 使用自定义集聚合函数组合每个键的元素的通用功能。
# - `createCombiner`, which turns a V into a C (e.g., creates a one-element list)
# 对初始值进行操作
# - `mergeValue`, to merge a V into a C (e.g., adds it to the end ofa list)
# 对分区内的元素进行合并
# - `mergeCombiners`, to combine two C's into a single one (e.g., merges the lists)
# 对分区间的元素进行合并
combine_by_key_rdd = x.combineByKey(createCombiner, mergeValue, mergeCombiners)
print(combine_by_key_rdd.collect()) # [('Fred', [274, 3]), ('Wilma', [286, 3])]
# 接下来平均值如何实现--('Fred', [274, 3])---x[0]=Fred x[1]= [274, 3],x[1][0]=274,x[1][1]=3
print(combine_by_key_rdd.map(lambda x: (x[0], int(x[1][0] / x[1][1]))).collect())
[[('Fred', 88), ('Fred', 95)], [('Fred', 91), ('Wilma', 93)], [('Wilma', 95), ('Wilma', 98)]]
reduceBykey: [('Fred', 274), ('Wilma', 286)]
[('Fred', [274, 3]), ('Wilma', [286, 3])]
[('Fred', 91), ('Wilma', 95)]
(9) join操作
- join:内连接
- leftOuterJoin:左连接
- rightOuterJoin:右连接
_12_joinOpratition.py
# -*- coding: utf-8 -*-
# Program function:演示join操作
from pyspark import SparkConf, SparkContext
from pyspark.storagelevel import StorageLevel
import time
if __name__ == '__main__':
print('PySpark join Function Program')
# TODO:1、创建应用程序入口SparkContext实例对象
conf = SparkConf().setAppName("miniProject").setMaster("local[*]")
sc = SparkContext.getOrCreate(conf)
sc.setCheckpointDir("/export/data/pyspark_workspace/PySpark-SparkCore_3.1.2/data/checkpoint")
# TODO: 2、从本地文件系统创建RDD数据集
x = sc.parallelize([(1001, "zhangsan"), (1002, "lisi"), (1003, "wangwu"), (1004, "zhangliu")])
y = sc.parallelize([(1001, "sales"), (1002, "tech")])
# TODO:3、使用join完成联合操作
join_result_rdd = x.join(y)
print(join_result_rdd.collect()) # [(1001, ('zhangsan', 'sales')), (1002, ('lisi', 'tech'))]
print(x.leftOuterJoin(y).collect())
print(x.rightOuterJoin(y).collect()) # [(1001, ('zhangsan', 'sales')), (1002, ('lisi', 'tech'))]
# 缓存--基于内存缓存-cache底层调用的是self.persist(StorageLevel.MEMORY_ONLY)
join_result_rdd.cache()
# join_result_rdd.persist(StorageLevel.MEMORY_AND_DISK_2)
# 如果执行了缓存的操作,需要使用action算子触发,在4040页面上看到绿颜色标识
join_result_rdd.collect()
# 如果后续执行任何的操作会直接基于上述缓存的数据执行,比如count
print(join_result_rdd.count())
join_result_rdd.unpersist()
print("释放缓存之后,直接从rdd的依赖链重新读取")
join_result_rdd.checkpoint()
join_result_rdd.count()
# time.sleep(600)
sc.stop()
[(1001, ('zhangsan', 'sales')), (1002, ('lisi', 'tech'))]
[(1001, ('zhangsan', 'sales')), (1002, ('lisi', 'tech')), (1003, ('wangwu', None)), (1004, ('zhangliu', None))]
[(1001, ('zhangsan', 'sales')), (1002, ('lisi', 'tech'))]
2
释放缓存之后,直接从rdd的依赖链重新读取
(10)accumulator累加器
_13_accumulate.py
# -*- coding: utf-8 -*-
# Program function:测试累加器
# 测试1:python集合的累加器,作为单机版本没有问题
# 测试2:spark的rdd的集合,直接进行累加操作,不会触发结果到driver的
# 原因;Driver端定义的变量,在executor执行完毕后没有将结果传递到driver
# 引出共享变量,Driver和Executor共享变量的改变
from pyspark import SparkContext, SparkConf
if __name__ == '__main__':
conf = SparkConf().setAppName("miniPy").setMaster("local[*]")
sc = SparkContext(conf=conf)
# 下面是rdd的集合
l1 = [1, 2, 3, 4, 5]
l1_textFile = sc.parallelize(l1)
# 定义累加器
acc_num = sc.accumulator(10)
print(type(acc_num))
# 执行函数
def add_num(x):
global acc_num
# acc_num+=x,这里了累加器提供的默认的方法是add方法
acc_num.add(x)
# 执行foreach
l1_textFile.foreach(add_num)
# 输出累加器的值
print(acc_num) # 25
print(acc_num.value) # 获取累加器的值,25
25
25
(11)广播变量broadcast
- collectAsMap():转化为dict类型
- broadcast:键值对形式
_14_testBroadcast.py
# -*- coding: utf-8 -*-
# Program function:
'''
广播变量的作用是提高Spark程序的性能。当需要在多个task上共享某个变量时,如果每个task都直接复制这个变量,那么会很浪费资源。因此,Spark提供了广播变量的机制,可以让Driver将变量广播给所有的Executor,从而使得每个Executor只需要拷贝一份变量,避免了大量的数据复制操作,提高了程序的执行效率。
'''
from pyspark import SparkContext, SparkConf
if __name__ == '__main__':
conf = SparkConf().setAppName("miniPy").setMaster("local[*]")
sc = SparkContext(conf=conf)
# 这里定义rdd
kvFruit = sc.parallelize([(1, "apple"), (2, "orange"), (3, "banana"), (4, "grape")])
print(kvFruit.collect())
fruit_collect_as_map = kvFruit.collectAsMap()
print(type(fruit_collect_as_map))
# 声明广播变量
broadcast_value = sc.broadcast(fruit_collect_as_map)
# print(fruit_collect_as_map)#{1: 'apple', 2: 'orange', 3: 'banana', 4: 'grape'}
# 通过指定索引来查询对应水果名称
friut_ids = sc.parallelize([2, 1, 4, 3])
# 执行查询
print("执行查询后的结果")
# ['orange', 'apple', 'grape', 'banana']
print(friut_ids.map(lambda fruit: fruit_collect_as_map[fruit]).collect())
print("执行广播变量之后查询后的结果")
# 使用广播变量的取值
print(friut_ids.map(lambda fruit: broadcast_value.value[fruit]).collect())
[(1, 'apple'), (2, 'orange'), (3, 'banana'), (4, 'grape')]
<class 'dict'>
执行查询后的结果
['orange', 'apple', 'grape', 'banana']
执行广播变量之后查询后的结果
['orange', 'apple', 'grape', 'banana']
(12) 搜狗案例
思路:
- 1-os.version可以克服多版本问题
- 2-读取数据:过滤单个词长度不为0,过滤整条数据块有6个
- 3-map映射函数:每个数据字段进行逐一正则表达式:split是切割,sub是替换
- 4-rdd注意统计count
- 5- 使用 flatMap 和 lambda 函数进行分词和停用词过滤
- 6-统计思路:map映射统一格式,reduceByKey将相同的key的value数据累加操作,sortBy进行自定义排序
_02readSougouFile.py
# -*- coding: utf-8 -*-
# Program function:
from pyspark import SparkContext, SparkConf
import os
import re
import jieba
os.environ['SPARK_HOME'] = '/export/server/spark'
PYSPARK_PYTHON = "/root/anaconda3/envs/pyspark_env/bin/python3"
# 当存在多个版本时,不指定很可能会导致出错
os.environ["PYSPARK_PYTHON"] = PYSPARK_PYTHON
os.environ["PYSPARK_DRIVER_PYTHON"] = PYSPARK_PYTHON
if __name__ == '__main__':
print('PySpark RDD Program')
# TODO:1、创建应用程序入口SparkContext实例对象
conf = SparkConf().setAppName("miniProject").setMaster("local[*]")
sc = SparkContext.getOrCreate(conf)
# TODO: 2、从文件系统加载数据,创建RDD数据集
# TODO: 3、调用集合RDD中函数处理分析数据
print("=====================1-读取数据==========================")
resultRDD2 = sc.textFile("file:///export/data/spark_practice/PySpark-SparkCore_3.1.2/data/sougou/SogouQ.reduced") \
.filter(lambda line: (len(line.strip()) > 0) and len(re.split("\\s+", line)) == 6) \
.map(lambda line: (
re.split("\\s+", line)[0],
re.split("\\s+", line)[1],
re.sub("\\[|\\]", "", re.split("\\s+", line)[2]),
re.split("\\s+", line)[3],
re.split("\\s+", line)[4],
re.split("\\s+", line)[5]))
print("count={},first={}".format(resultRDD2.count(), resultRDD2.first()))
print(type(resultRDD2.collect()))
print("=====================2-搜索关键词统计==========================")
# TODO: 4、获取用户【查询词】,使用JieBa进行分词,按照单词分组聚合统计出现次数,类似WordCount程序
# reduceByKey将相同的key的value数据累加操作
# 定义正则表达式模式,匹配中文字符
pattern = "[\u4e00-\u9fa5]+"
regex = re.compile(pattern)
# 定义停用词表路径
stopwords_path = '/export/data/spark_practice/PySpark-SparkCore_3.1.2/data/stop_table/哈工大停用词表.txt'
# 加载停用词表
with open(stopwords_path, 'r', encoding='utf-8') as file:
stopwords = [line.strip() for line in file]
# 使用 flatMap 和 lambda 函数进行分词和停用词过滤
flat_map = resultRDD2.flatMap(
lambda record: [word for word in jieba.cut(record[2]) # jieba分词
if regex.match(word) # 匹配中文
and word not in stopwords # 去除停用词
and len(word) > 1] # 去除单个词
)
key1 = flat_map.map(lambda word: (word, 1)) \
.reduceByKey(lambda k1, k2: k1 + k2) \
.sortBy(lambda x: x[1], ascending=False) \
.take(10)
print(key1)
# key2 = flat_map.map(lambda word: (word, 1)) \
# .reduceByKey(lambda k1, k2: k1 + k2) \
# .map(lambda x,y:(y,x))
# #.sortByKey(True, 3, keyfunc=lambda k: k.lower())\
# print(key2.collect())
# TODO: 5、用户搜索点击统计
print("=====================2-用户搜索点击统计==========================")
# 统计出每个用户每个搜索词点击网页的次数,可以作为搜索引擎搜索效果评价指标。先按照用户ID分组,再按照【查询词】分组,最后统计次数,求取最大次数、最小次数及平均次数
flat_map = resultRDD2.map(lambda record: (record[1], record[2]))
# print(flat_map.take(1)) 测试打印
key3 = flat_map.map(lambda word: (word, 1)) \
.reduceByKey(lambda k1, k2: k1 + k2) \
.sortBy(lambda x: x[1], ascending=False) \
.take(10)
print(key3)
print("=====================3-搜索时间段统计==========================")
# 按照【访问时间】字段获取【小时】,分组统计各个小时段用户查询搜索的数量,进一步观察用户喜欢在哪些时间段上网,使用搜狗引擎搜索
hourSearchRDD = resultRDD2.map(lambda record: str(record[0])[0:2])
print(hourSearchRDD.take(5))
key4 = hourSearchRDD.map(lambda word: (word, 1)) \
.reduceByKey(lambda k1, k2: k1 + k2) \
.sortBy(lambda x: x[1], ascending=False) \
.take(10)
print(key4)
sc.stop()
=====================1-读取数据==========================
count=1721708,first=('00:00:00', '2982199073774412', '360安全卫士', '8', '3', 'download.it.com.cn/softweb/software/firewall/antivirus/20067/17938.html')
<class 'list'>
=====================2-搜索关键词统计==========================
[('地震', 89367), ('救灾物资', 69092), ('哄抢', 69084), ('汶川', 68409), ('原因', 62688), ('下载', 39462), ('图片', 31979), ('视频', 29157), ('暗娼', 26825), ('朗斯', 20049)]
=====================2-用户搜索点击统计==========================
[(('7822241147182134', 'free+girls'), 274), (('9165829432475153', '人妖摄影'), 177), (('49180486532951556', 'babes'), 160), (('519493440787543', '镣铐绳艺视频'), 156), (('19447614244798927', '绳艺kb视频'), 141), (('7076435807359547', '库娃+三围'), 135), (('5515612701706876', '玄幻小说'), 134), (('900755558064074', 'liuhecai'), 123), (('1756178764125793', '屋面种植土'), 118), (('5634142212517204', '亭子施工图'), 116)]
=====================3-搜索时间段统计==========================
['00', '00', '00', '00', '00']
[('16', 116540), ('21', 115126), ('20', 110863), ('15', 109086), ('10', 104694), ('17', 104639), ('14', 101295), ('22', 99977), ('11', 97991), ('19', 97129)]
(13)网站点击案例
思路:文章来源:https://www.toymoban.com/news/detail-843200.html
- 计算pv:计算多少行,map格式化,reduceByKey聚合
- 计算uv:筛选ip,去重后计数
- 计算topK:下标记得提前筛选长度,需要使用"-"
# -*- coding: utf-8 -*-
# Program function:完成网站访问指标的统计,Pv,Uv,TopK
'''
* 1-准备SparkContext的环境
* 2-读取网站日志数据,通过空格分隔符进行分割
* 3-计算Pv,统计有多少行,一行就算做1次Pv
* 4-计算Uv,筛选出ip,统计去重后Ip
* 5-计算topk,筛选出对应业务的topk
'''
from pyspark import SparkConf, SparkContext
if __name__ == '__main__':
# *1 - 准备SparkContext的环境
conf = SparkConf().setAppName("click").setMaster("local[*]")
sc = SparkContext.getOrCreate(conf=conf)
sc.setLogLevel("WARN") # 直接将log4j放入文件夹中
# *2 - 读取网站日志数据,通过空格分隔符进行分割
file_rdd = sc.textFile("/export/data/spark_practice/PySpark-SparkCore_3.1.2/data/click/access.log")
# *3 - 计算Pv,统计有多少行,一行就算做1次Pv
rdd_map_rdd = file_rdd.map(lambda line: ("pv", 1))
print("pv result is:", rdd_map_rdd.reduceByKey(lambda x, y: x + y).collect()) # pv result is: [('pv', 14619)]
# *4 - 计算Uv,筛选出ip,统计去重后Ip
# file_rdd_map = file_rdd.map(lambda line: line.split(" ")[0])
file_rdd_map = file_rdd \
.map(lambda line: line.split(" ")) \
.map(lambda x: x[0])
# print(file_rdd_map.take(5))
uv_num = file_rdd_map \
.distinct() \
.map(lambda line: ("uv", 1))
print("uvCount:", uv_num.reduceByKey(lambda x, y: x + y).collect()) # uvCount: [('uv', 1051)]
# *5 - 计算topk,筛选出对应业务的topk,访问网站【10下标】的Topk,"需要使用\"-\"
re = file_rdd \
.map(lambda x: x.split(" ")) \
.filter(lambda line: len(line) > 10) \
.map(lambda line: (line[10], 1)) \
.reduceByKey(lambda x, y: x + y) \
.sortBy(lambda x: x[1], False) \
.filter(lambda x: x[0] != "\"-\"")
print(re.take(10))
re1 = file_rdd \
.map(lambda x: x.split(" ")) \
.filter(lambda line: len(line) > 10) \
.map(lambda line: (line[10], 1)) \
.groupByKey() \
.mapValues(sum) \
.sortBy(lambda x: x[1], False) \
.filter(lambda x: x[0] != "\"-\"")
print(re1.take(10))
pv result is: [('pv', 14619)]
uvCount: [('uv', 1051)]
[('"http://blog.fens.me/category/hadoop-action/"', 547), ('"http://blog.fens.me/"', 377), ('"http://blog.fens.me/wp-admin/post.php?post=2445&action=edit&message=10"', 360), ('"http://blog.fens.me/r-json-rjson/"', 274), ('"http://blog.fens.me/angularjs-webstorm-ide/"', 271), ('"http://blog.fens.me/wp-content/themes/silesia/style.css"', 228), ('"http://blog.fens.me/nodejs-express3/"', 198), ('"http://blog.fens.me/hadoop-mahout-roadmap/"', 182), ('"http://blog.fens.me/vps-ip-dns/"', 176), ('"http://blog.fens.me/nodejs-underscore/"', 165)]
[('"http://blog.fens.me/category/hadoop-action/"', 547), ('"http://blog.fens.me/"', 377), ('"http://blog.fens.me/wp-admin/post.php?post=2445&action=edit&message=10"', 360), ('"http://blog.fens.me/r-json-rjson/"', 274), ('"http://blog.fens.me/angularjs-webstorm-ide/"', 271), ('"http://blog.fens.me/wp-content/themes/silesia/style.css"', 228), ('"http://blog.fens.me/nodejs-express3/"', 198), ('"http://blog.fens.me/hadoop-mahout-roadmap/"', 182), ('"http://blog.fens.me/vps-ip-dns/"', 176), ('"http://blog.fens.me/nodejs-underscore/"', 165)]
(14)网站点击案例
思路:文章来源地址https://www.toymoban.com/news/detail-843200.html
- 1-读取两份文件
- 2-city_ip_rdd作为广播变量
- 3-用户ip转化为long类型
- 4-二分查找方法在city_ip_rdd查找对应的经度和维度,让其匹配index,然后定位经纬度
- 5-mapPartitions(函数):函数应用于分区为单位
- 6-reduceByKey(lambda x,y:x+y)
ipchecklocation.py
# -*- coding: utf-8 -*-
# Program function:实现用户ip的地址查询,实现相同经纬度范围统计
'''
* 1-准备Spark的上下文环境
* 2-读取用户所在ip信息的文件,切分后选择下标为1字段就是用户ip
* 3-读取城市ip段信息,需要获取起始ip的long类型(下标2),结束ip的long类型(下标3),经度(下标13),维度(下标14)
* 4-通过ip地址转化为long类型IP
* 5-采用折半查找方法寻找ip对应的经纬度
* 6-根据相同经纬度的数据进行累加统计在进行排序
* 7-画个图
'''
from pyspark import SparkConf, SparkContext
from pyspark.sql import SparkSession
# 需要拿到用户ip转化为long类型,然后通过二分查找方法在city_ip_rdd查找对应的经度和维度
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
def binary_search(ip_num, city_rdd_broadcast_value):
start = 0
end = len(city_rdd_broadcast_value) - 1
# (1)起始ip的long(2)结束ip的long(3)经度(4)维度的信息
# city_rdd_broadcast_value
while (start <= end):
mid = int((start + end) / 2)
# 首先判断是否位于middle的[0]和[1]下标之间
if (ip_num >= int(city_rdd_broadcast_value[mid][0]) and ip_num <= int(city_rdd_broadcast_value[mid][1])):
# 指导找到中间位置,返回mid位置=后面的index
return mid
# 如果是小于middle[0]起始ip, end=mid
if (ip_num < int(city_rdd_broadcast_value[mid][0])):
end = mid
# 如果是大于middle[1]结束ip, start=mid
if (ip_num > int(city_rdd_broadcast_value[mid][1])):
start = mid
def main():
global city_ip_rdd_broadcast
# *1 - 准备Spark的上下文环境
spark = SparkSession.builder.appName("ipCheck").master("local[*]").getOrCreate()
sc = spark.sparkContext
sc.setLogLevel("WARN") # 直接将log4j放入文件夹中
# *2 - 读取用户所在ip信息的文件,切分后选择下标为1字段就是用户ip
# user_rdd读取的是包含有user的ip地址的信息
user_rdd = sc.textFile(
"/export/data/spark_practice/PySpark-SparkCore_3.1.2/data/ip/20190121000132394251.http.format")
dest_ip_rdd = user_rdd \
.map(lambda x: x.split("|")) \
.map(lambda x: x[1])
# print(dest_ip_rdd.take(4))#['125.213.100.123', '117.101.215.133', '117.101.222.68', '115.120.36.118']
# *3 - 读取城市ip段信息,需要获取起始ip的long类型(下标2),结束ip的long类型(下标3),经度(下标13),维度(下标14)
# .map(lambda x:(x[2],x[3],x[len(x)-2],x[len(x)-1]))
ip_rdd = sc.textFile("/export/data/spark_practice/PySpark-SparkCore_3.1.2/data/ip/ip.txt")
city_ip_rdd = ip_rdd \
.map(lambda x: x.split("|")) \
.map(lambda x: (x[2], x[3], x[13], x[14]))
print(type(city_ip_rdd))
# Broadcast a read-only variable to the cluster,下面的代码中使用city.collect将rdd转化为list在进行广播
city_ip_rdd_broadcast = sc.broadcast(city_ip_rdd.collect())
print(type(city_ip_rdd_broadcast))
# city_ip_rdd是包含有城市的(1)起始ip的long(2)结束ip的long(3)经度(4)维度的信息
# print(city_ip_rdd.take(5))
def GetPos(x): # x=【192.168.88.161,192.168.88.161】
city_rdd_broadcast_value = city_ip_rdd_broadcast.value
def getResult(ip):
# *4 - 通过ip地址转化为long类型IP
ip_num = ip_transform(ip)
# *5 - 采用折半查找方法寻找ip对应的经纬度
# index 获取 (1)起始ip的long(2)结束ip的long(3)经度(4)维度的信息
index = binary_search(ip_num, city_rdd_broadcast_value)
return ((city_rdd_broadcast_value[index][2], city_rdd_broadcast_value[index][3]), 1)
# 得到的是((经度,维度),1),下面是python的map函数
re = map(tuple, [getResult(ip) for ip in x]) # 【ip=192.168.88.161,ip=192.168.88.161】
return re
# *6 - 根据相同经纬度的数据进行累加统计在进行排序 【192.168.88.161,192.168.88.161】
ip_rdd_map_partitions = dest_ip_rdd.mapPartitions(GetPos)
result = ip_rdd_map_partitions.reduceByKey(lambda x, y: x + y).sortBy(lambda x: x[1], False)
print("final sorted result is:")
print(result.take(5))
# [(('108.948024', '34.263161'), 1824), (('116.405285', '39.904989'), 1535), (('106.504962', '29.533155'), 400), (('114.502461', '38.045474'), 383), (('106.57434', '29.60658'), 177)]
# *7 - 画个图
sc.stop()
if __name__ == '__main__':
main()
<class 'pyspark.rdd.PipelinedRDD'>
<class 'pyspark.broadcast.Broadcast'>
final sorted result is:
[(('108.948024', '34.263161'), 1824), (('116.405285', '39.904989'), 1535), (('106.504962', '29.533155'), 400), (('114.502461', '38.045474'), 383), (('106.57434', '29.60658'), 177)]
到了这里,关于Spark重温笔记(二):快如闪电的大数据计算框架——你真的了解SparkCore的 RDD 吗?(包含企业级搜狗案例和网站点击案例)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!