Spark框架 及 pyspark库

简介

pyspark官方文档

  • 专为大规模数据处理而设计的快速通用计算引擎(与HadoopMapReduce功能类似)
  • 可以导入外部存储系统的数据集,例如:HDFSHBase或其他Hadoop数据源
  • 基于内存的分布式计算框架(只计算,不涉及存储)
    在这里插入图片描述
  • Spark框架组件丰富
    • 批处理:Spark coreSpark 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时:包含MasterWorker表示开启成功
在这里插入图片描述

spark-core(RDD)

RDDResilient Distributed Dataset)叫做弹性分布式数据集,是Spark最基本的数据抽象,它代表一个 不可变可分区 、里面的元素可 并行计算 的数据集合.

  • Dataset数据集:一个数据集,简单的理解为集合,用于存放数据
  • Distributed分布式存储:RDD的数据是分布式存储,并且可以做分布式的计算
  • Resilient:弹性
    • 它的数据可以保存在磁盘,也可以保存在内存中
    • 数据分布式也是弹性的
  • 不可变RDD1变换为RDD2RDD1依然存在
  • 可分区:多个分区,数据可以存储在多个不同的机器内存上
  • 并行计算

RDD特性:

  • 分区:数据可以存储在不同的机器内存上
  • 依赖:每一步计算都依赖于上一步计算的结果
  • 缓存:数据可以利用灵活的缓存策略进行存储
    • 默认缓存到内存
    • 对于k:v 型 RDD 具有该分区特性(内存,磁盘,内存和磁盘)

RDD的核心属性:调度和计算都依赖于这五个属性

  • 分区列表:多个分区放在一起,便于管理
  • 依赖列表:记录着每一个计算中的依赖关系
  • Compute函数,用于计算RDD各分区的值
  • 分区策略(可选):根据自定义策略可以调整分区大小,分区个数。
  • 优先位置列表(可选,HDFS实现数据本地化,避免数据移动)

RDD的 常用算子

RDD的三类算子包括 transformationactionpersist,

  • 前两种进行数据处理
  • persist处理数据存储相关操作
transformation
  • 通过已有的RDD生成一个新的RDD
  • 懒计算:只会记录RDD转化关系,并不立即计算,当执行action操作时才会计算
  • 举例:mapfiltergroupByreduceBy
  • 优点:可以中间插入优化过程
value型 transformation
  1. 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]
    
  2. mapPartitions 分区式执行map函数
    格式:RDD对象. mapPartitions(函数名 或 匿名函数)
    作用:类似于map, map作用于每个分区的每个元素,但 mapPartitions作用于每个分区
    例如:rddN个元素,分布于M个分区,若用map则函数会调用N次, 而使用mapPartitions函数仅调用M次,当在映射的过程中不断地创建对象时就可以使用mapPartitions,比map的效率要高很多。比如当向数据库写入数据

  3. 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']
    
  4. 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']
    

    flatmapmap的区别:flatmapmap的基础上将结果合并到一个列表中

    >>> 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元素个数多)

  5. 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')]
    
  6. 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)]
    
  7. 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)]
    
  8. distinct去重rdd中的元素
    格式:distinct(numPartitions = None )
    作用:去重,返回一个包含该RDD中不同元素的新RDD

    >>> sorted(sc.parallelize([1, 1, 2, 3]).distinct().collect())
    [1, 2, 3]
    
  9. sample 采样数据
    格式:rdd1.sample(抽样是否放回,采样比例,随机种子)

    rdd2 = rdd.sample(False,0.2,100)
    
  10. glom 将分区的元素组成一个数据
    在这里插入图片描述

key-value型 transformation

以下命令处理的rdd一定是 二元素元组 的列表,并根据第0个元素(key)操作第1个元素(value

  1. 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]
    
  2. 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)]
    
  3. 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)]
    
  4. sortby排序

  5. 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))]
    
  6. leftOuterJoin:执行左外连接的rdd1rdd2
    格式: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))]
    
  7. 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操作是立即执行的
  • 举例:countreducesaveAsTextFile
  • 缺点:不能插入优化过程
  1. collect
    作用:返回一个listlist中包含 RDD中的所有元素
    只有当数据量较小的时候使用
    所有的结果都会加载到内存中

  2. reduce
    作用:reduceRDD中元素两两传递给输入函数,同时产生一个新的值,新产生的值与RDD中下一个元素再被传递给输入函数直到最后只有一个值为止。

    >>> rdd1 = sc.parallelize([1,2,3,4,5])
    >>> rdd1.reduce(lambda x,y : x+y)
    15
    
  3. first
    作用:返回RDD的第一个元素

    >>> sc.parallelize([2, 3, 4]).first()
    2
    
  4. 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]
    
  5. count
    作用:返回RDD中元素的个数

    >>> sc.parallelize([2, 3, 4]).count()
    3
    
  6. 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]
    
  7. saveAsTextFile
    格式:rdd.saveAsTextFile('HDFS文件路径/文件名')
    作用:函数将RDD保存为文本至HDFS指定目录,每次输出一行,每个分区存储一个文件

  8. 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语法以及自定义函数(直接接入HivemetaStore)
  • Standard Connectivity 标准接口:提供了 JDBCODBC的数据接口,作为数据服务将数据通过JDBC/ODBC方式传递

DataFrame简介

Spark语义中,DataFrame是一个分布式的行集合,可以想象为一个关系型数据库的表

特点:

  • 不可变:一旦DataFrame被创建,就不能更改,只能生成新的DataFrame
  • 懒加载:只有action才会触发Transformation的执行

DataFrame常用算子

DataFrameAPI也分为 TransformationAction两类
在这里插入图片描述

  1. printSchema 显示数据结构
    格式:DataFlame.printSchema()

    >>> schemaPeople.printSchema()
    root
     |-- age: long (nullable = true)
     |-- name: string (nullable = true)
    
  2. show
    格式:DataFlame.show(n)
    作用:显示前n条数据

    >>> schemaPeople.show(2, truncate=False)     # 完整(当列值太长时会省略显示)显示两条数据
    +---+--------+
    |age|    name|
    +---+--------+
    | 25|   Ankit|
    | 22|Jalfaizy|
    +---+--------+
    
  3. count
    格式:DataFlame.count()
    作用:统计DataFlame总行数

    >>> schemaPeople.count()
    4
    
  4. columns
    格式:DataFlame.columns
    作用:统计DataFlame总行数

    >>> schemaPeople.columns
    ['age', 'name']
    
  5. 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")
    
  6. drop 删除一列
    格式:DataFlame.drop(指定列名) 如果指定列名不存在,则不删列不报错

    >>> df.drop("age").show()
    +--------+----+
    |    name|age2|
    +--------+----+
    |   Ankit|  50|
    |Jalfaizy|  44|
    | saurabh|  40|
    |    Bala|  52|
    +--------+----+
    
  7. 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|
    +-------+-----------------+
    
  8. 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()
    
  1. 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|
    +---+------+-----+
    
  2. dropna 删除空值超过指定个数的行数据
    格式:dropna(how ='any',thresh = None,subset = None )

    • howany 或 allany表示 Null值就删 行数据。all表示 Null值才删 行数据

    • threshint型数据 表示非空值数,如果指定则覆盖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=["指定字段"])
      
  3. fillna 替换null值 别名为na.fill()
    格式:fillna(value,subset = None )

    • valueint,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|
      +---+------+-------+
      
  4. 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值

  5. 自定义的汇总方法
    格式: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()
    
  6. 自定义的函数(udf
    RDD中可以直接使用自定义函数,交给rddtransformatioins方法进行执行
    DataFrame中则需要通过udf将自定义函数封装成udf函数再交给DataFrame进行调用执行

    from pyspark.sql.functions import udf
    
    def 自定义函数名(参数1,...):
        pass
    
    check = udf(自定义函数名,StringType())    # StringType 表示函数的返回值类型为字符串
    
    resultDF = DF1.withColumn('New_cls',check(参数1,...))
    
  7. randomSplit 拆分数据集,拆成两部分
    格式 :df1,df2 = DataFlame.randomSplit([df1占比例, df2占比例])

    trainDF, testDF = df.randomSplit([0.6, 0.4])
    
  8. sample 采样数据(可用于绘制散点图)
    格式:DataFlame.sample(抽样是否放回,采样比例,随机种子100)

    sdf = df.sample(False,0.2,100)
    
  9. subtract 查看两个数据集在类别上的差异
    格式:DataFlame1.select(指定列).subtrac(DataFlame2.select(指定列))
    商用场景:确保训练数据集覆盖了所有分类

    diff_in_train_test = testDF.select('cls').subtract(trainDF.select('cls'))
    diff_in_train_test.distinct().count()
    
  10. join数据集拼接

    df1 = df1.join(df2, 合并条件, 'outer')  # 外连接+dropna  比 内连接 更快
    df1 = df1.dropna()
    
  11. crosstab 交叉表
    格式:DataFrame.crosstab(指定列1, 指定列2)

    • 指定列1的不同的项:为每行的首项数据(spark dataframe 没有行索引
    • 指定列2的不同的项:为交叉表的 列名(第二项及其之后)
    • 数据:为以列1做分组,统计列2不同项中 每项个数
      df.crosstab('cls','SepalLength').show()
      
  12. 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)
    
  13. 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-corespark-sql都是处理属于离线批处理任务,数据一般都是在固定位置上,通常我们写好一个脚本,每天定时去处理数据,计算,保存数据结果。这类任务通常是T+1(一天一个任务),对实时性要求不高

但也存在很多实时性处理的需求,例如:双十一的京东阿里,通常会做一个实时的数据大屏,显示实时订单。这种情况下,对数据实时性要求较高,仅仅能够容忍到延迟1分钟或几秒钟。

组件

在这里插入图片描述

Streaming ContextSpark-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操作
      ss19

    • RDD操作
      ss9
      RDD中提供了丰富的API接口,DstreamRDD的操作进行了封装,但封装并不完全,如果有些方法没有封装进去,但是还是需要使用这些方法,就可以调用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,则将消除相应的状态键值对
      步骤:

    1. 要定义一个state,可以是任意的数据类型
    2. 要定义state更新函数–指定一个函数如何使用之前的state和新值来更新state
    3. 对于每个batch,Spark都会为每个之前已经存在的key去应用一次state更新函数,无论这个key在batch中是否有新的数据。如果state更新函数返回none,那么key对应的state就会被删除
    4. 对于每个新出现的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编码步骤

  1. 创建一个StreamingContext
  2. 根据不同的数据源 创建 数据对象(Dstream
  3. 对数据对象进行Transformations操作
  4. 输出结果(Dstream.pprint()
  5. 开始和停止(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编码步骤

  1. 创建一个StreamingSession
  2. 根据不同的数据源 创建 数据对象(DataFrom
  3. 定义流计算过程
  4. 启动流计算并输出结果
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()
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值