1. 简介
1.1 定义
- 专为大规模数据处理而设计的快速通用计算引擎
- 与Hadoop的 MapReduce功能类似
MapReduce V.S. Spark
spark快
Spark框架组件丰富
- spark特点
- 1、速度快(比mapreduce在内存中快100倍,在磁盘中快10倍)
- 2、易用性(可以通过java/scala/python/R开发spark应用程序)
- 3、通用性(可以使用spark sql/spark streaming/mlib/Graphx)
- 4、兼容性(spark程序可以运行在standalone/yarn/mesos)
启动pyspark
words = sc.textFile('file:///root/README.txt').flatMap(lambda line: line.split(' ')).map(lambda x: (x, 1)).reduceByKey(lambda a, b : a+b).collect()
2 RDD
RDD(Resilient Distributed Dataset)弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合.
- Dataset:一个数据集,简单的理解为集合,用于存放数据
- Distributed:它的数据是分布式存储,并且可以做分布式的计算
- Resilient:弹性的
- 数据可以保存在磁盘,也可以保存在内存中
3 RDD的创建
sparkContext
-
conf = SparkConf().setAppName(appName).setMaster(master) sc = SparkContext(conf=conf)
sc = SparkContext(appName='xxx', master='xxx.xxx.xxx.xxx')
-
进入pyspark环境会自动创建sc对象
192.168.19.137:4040
distData = sc.parallelize(data,5)
通过sc parallelize方法把已经在内存的数据加载生成rdd
生成rdd时可以指定分区数量,一般是cpu核的2-4倍
太多就会导致更多的资源花在任务调度上
sc.textFile('file:///root/tmp/word.txt')
把文件加载生成rdd
1 RDD常用算子练习
1 RDD的三类算子介绍
RDD的三类算子包括 transformation action 和 persist, 前两种进行数据处理, persist处理数据存储相关操作
RDD 支持两种类型的操作:
- transformation
- 从一个已经存在的数据集创建一个新的数据集
- action
- 获取对数据进行运算操作之后的结果
特性
- 所有的transformation操作都是惰性的(lazy)
- 不会立即计算结果
- 只记下应用于数据集的transformation操作
- 只有调用action一类的操作之后才会计算所有transformation
- 这种设计使Spark运行效率更高
- 例如map reduce 操作,map创建的数据集将用于reduce,map阶段的结果不会返回,仅会返回reduce结果。
所有transformation操作生成的结果,打印出来只能得到一个值的属性,就是一个rdd
action操作会把数据集的内容打印出来
如果一串操作,最终把数据集内容打印出来,最后一个是action,前面的都是transformation
persist 操作 对rdd进行缓存
2 RDD Transformation算子
map(function)
- 将function函数作用到数据集的每一个元素上,生成一个新的RDD返回
- function可以是lambda表达式,也可以是一个函数
filter(function)
- 选出所有function返回值为true的元素,生成一个新的RDD返回
flatMap(function)
- flatMap会先执行map的操作,再将所有对象合并为一个对象
union
- 对两个RDD求并集
intersection
- 对两个RDD求交集
groupByKey
-
以元组中的第0个元素作为key,进行分组,返回一个新的RDD
-
groupByKey之后的结果中 value是一个Iterable
-
list(result[2][1])
reduceByKey(function)
-
第0个元素作为key,将key相同的键值对,按照function进行计算
-
rdd.reduceByKey(lambda x,y:x+y).collect() x,y 不是指的每个value 而是,x上一轮的结果,y是当前的value 是迭代计算的过程
sortByKey
-
sc.parallelize(tmp2).sortByKey(True, 3, keyfunc=lambda k: k.lower()).collect()
-
keyfunc这里处理的结果只会影响排序,并不会改变原始的key
3 RDD Action算子
collect
- 返回一个list,list中包含 RDD中的所有元素
reduce(function)
- reduce将RDD中元素两两传递给输入function,同时产生一个新的值,新产生的值与RDD中下一个元素再被传递给输入函数直到最后只有一个值为止。
first
- 返回RDD的第一个元素
take(n)
- 返回RDD的前N个元素
count
- 返回RDD中元素的个数
2 spark-core 案例_点击日志分析
1 配置
1 配置pycharm远程连接虚拟机,通过ssh上传代码到虚拟机,在命令行执行代码
2 运行后提示 JAVA_HOME is not set,没有设置JAVA_HOME(Java运行环境)
PYSPARK_PYTHON and PYSPARK_DRIVER_PYTHON are correctly set. 需要指定当前使用的Python解释器位置
2 通过spark实现点击日志分析
- PV page view 页面访问量 访问一次记一次pv
- UV Unique Visitor 独立访客访问数,一台设备算一次UV
- topN 访问量最大的前N个页面
pv:网站的总访问量,把日志中每一行都转成(‘PV’, 1),统计出’PV’的个数
sc = SparkContext('local[2]','PV')
rdd1 = sc.textFile('file:///root/sz39-spark/spark-data/access.log')
rdd2 = rdd1.map(lambda line: ('PV', 1))
rdd3 = rdd2.reduceByKey(lambda a, b: a+b)
result = rdd3.collect()
print(result)
uv: 网站的独立用户访问量,把日志中的ip解析出来,然后去重,每个ip都转成(‘UV’, 1)统计’UV’个数
from pyspark import SparkContext
sc = SparkContext('local[2]','UV')
rdd1 = sc.textFile('file:///root/sz39-spark/spark-data/access.log')
rdd2 = rdd1.map(lambda line: line.split(' ')[0])
rdd3 = rdd2.distinct().map(lambda ip: ('UV', 1))
rdd4 = rdd3.reduceByKey(lambda a,b:a+b)
rdd4.saveAsTextFile("hdfs:///uv2/result") # action操作
result = rdd4.collect()
print(result)
topn:访问次数最多的链接的topN,把日志中的链接解析出来,把每个链接和1一起生成tuple,把链接作为key统计个数,接着排序
from pyspark import SparkContext
sc = SparkContext('local[2]','UV')
rdd1 = sc.textFile('file:///root/sz39-spark/spark-data/access.log')
rdd2 = rdd1.map(lambda line: line.split(' ')).filter(lambda x : len(x)>10).map(lambda x: (x[10], 1))
rdd3 = rdd2.reduceByKey(lambda a, b: a+b).sortBy(lambda x: x[1], ascending=False)
#result = rdd3.take(5)
print(rdd3.take(5))
print(rdd3.collect())
3 Spark-core案例 IP统计
1 需求
把后台访问日志中的ip转化成经纬度,统计相同经纬度的个数,然后在地图上以热力图的形式展示
已有数据
访问日志,中访问的ip地址
ip.txt,存的是ip范围和经纬度的对应关系
从日志中提取出ip地址,把ip地址转成数,把这个数放到ip.txt中用二分查找法找到对应的经纬度,统计出经纬度的个数
ip_to_num计算过程
# 1.0.1.0
# [1, 0, 1, 0]
# ip_num = 0
i = 1
ip_num = 0
000000000
int(1) | ip_num << 8
000000001
ip_num = 000000001
i = 0
int(0) | ip_num << 8
00000000100000000
ip_num = 00000000100000000
i = 1
int(1) | ip_num << 8
0000000010000000000000000
ip_num = 0000000010000000000000001
i = 0
int(0) | ip_num << 8
000000001000000000000000100000000
ip_num = 000000001000000000000000100000000
def ip_to_num(ip):
ips = ip.split('.')
ip_num = 0
for i in ips:
ip_num = int(i) | ip_num << 8
return ip_num
整体流程
1、 加载城市ip段信息,获取ip起始数字和结束数字,经度,纬度
2、 加载日志数据,获取ip信息,然后转换为数字,和ip段比较
3、 比较的时候采用二分法查找,找到对应的经度和纬度
4,对相同的经度和纬度做累计求和
二分查找法
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
if ip_num > int(broadcast_value[mid][1]):
start = mid