PySpark | RDD

本文详细介绍了PySpark中的RDD(弹性分布式数据集),包括RDD的五大特性、WordCount案例中的应用,以及RDD编程入门,涵盖SparkContext、RDD创建、Transformation与Action算子、分区操作等关键知识点,旨在帮助读者深入理解RDD并掌握其使用。
摘要由CSDN通过智能技术生成


传送门:


一、RDD详解

1.为什么需要RDD?

  分布式计算需要:

  • 分区控制:多台机器同时并行计算
  • Shuffle控制:不同分区之间数据的关联
  • 数据存储\序列化\发送:
  • 数据计算API:

等一系列功能。这些功能,不能简单的通过Python内置的本地集合对象(如 List\ 字典等)去完成。我们在分布式框架中,需要有一个统一的数据抽象对象,来实现上述分布式计算所需功能。这个抽象对象, 就是RDD。

2.什么是RDD?

  RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,代表一个不可变、可分区、里面的元素可并行计算的集合。所有的运算以及操作都建立在 RDD 数据结构的基础之上。

  • Dataset:一个数据集合,用于存放数据的。
  • Distributed:RDD中的数据是分布式存储的,可用于分布式计算
  • Resilient:RDD中的数据可以存储在内存中或者磁盘中

在这里插入图片描述

3.RDD的5大特性

  RDD 数据结构内部有五个特性:前三个特性每个RDD都具备的,后两个特性可选的。
在这里插入图片描述

  • 特性1:RDD是有分区的
    RDD的分区是数据存储的最小单位,一份RDD数据本质上是分隔成了多个分区。
    在这里插入图片描述

  • 特性2:RDD的方法会作用在其所有的分区上
    如图,RDD3个分区在执行了map操作将数据都乘以10后,可以看到,3个分区的数据都乘以了10,体现了.map方法,是作用在了每一个分区之上。
    在这里插入图片描述

  • 特性3:RDD之间是有依赖关系的(迭代计算关系)

    sc = SparkContext(conf=conf)
    rdd1 = sc.textFile('hdfs://node1:8020/test/input/wordcount.txt')
    # 将单词进行切割,得到一个存储全部单词的集合对象
    rdd2 = rdd1.flatMap(lambda line: line.split(" "))
    # 将单词转换为元祖对象,key是单词,value是数字1
    rdd3 = rdd2.map(lambda x: (x, 1))
    # 将元祖的value按照key来分组,对所有value执行聚合相加操作
    rdd4 = rdd3.reduceByKey(lambda a, b: a + b)
    # 通过collect方法手机RDD的数据打印输出结果
    print(rdd4.collect())
    

    如上代码,RDD之间是有依赖的。比如,RDD2会产生RDD3,但是RDD2依赖RDD1。同样,RDD3会产生RDD4,但是RDD3依赖RDD2会形成一个依赖链条。这个链条称之为RDD的血缘关系。

  • 特性4:Key-Value型的RDD可以有分区器
    默认分区器: Hash分区规则,可以手动设置一个分区器(rdd.partitionBy的方法来设置)。这个特性是可能的,因为不是所有RDD都是Key-Value型。
    Key-Value RDD:RDD中存储的是二元元组(只有2个元素的元组,比如: (“hadoop”,6)),这就是Key-Value型RDD。

  • 特性5:RDD的分区规划,会尽量靠近数据所在的服务器
    在初始RDD(读取数据的时候)规划的时候,分区会尽量规划到存储数据所在的服务器上。因为这样可以走本地读取,避免网络读取

    总结, Spark会在确保并行计算能力的前提下,尽量确保本地读取
    这里是尽量确保,而不是100%确保
    所以这个特性也是:可能的

4.WordCount案例中的RDD

  以WordCount案例的执行流程来分析,

sc = SparkContext(conf=conf)
rdd1 = sc.textFile('hdfs://node1:8020/test/input/wordcount.txt')
# 将单词进行切割,得到一个存储全部单词的集合对象
rdd2 = rdd1.flatMap(lambda line: line.split(" "))
# 将单词转换为元祖对象,key是单词,value是数字1
rdd3 = rdd2.map(lambda x: (x, 1))
# 将元祖的value按照key来分组,对所有value执行聚合相加操作
rdd4 = rdd3.reduceByKey(lambda a, b: a + b)
# 通过collect方法手机RDD的数据打印输出结果
print(rdd4.collect())

在这里插入图片描述

5.总结

  • 如何正确理解RDD?
    弹性分布式数据集,分布式计算的实现载体(数据抽象)
  • RDD五大特点分别是?
    • 特性1:RDD是有分区的
    • 特性2:RDD的方法会作用在其所有的分区上
    • 特性3:RDD之间是有依赖关系的(迭代计算关系)
    • 特性4:Key-Value型的RDD可以有分区器
    • 特性5:RDD的分区规划,会尽量靠近数据所在的服务器

二、RDD编程入门

1.程序入口 SparkContext对象

  Spark RDD 编程的程序入口对象是SparkContext对象(不论何种编程语言)。只有构建出SparkContext,基于它才能执行后续的API调用和计算。本质上,SparkContext对编程来说,主要功能就是创建第一个RDD出来。
在这里插入图片描述

2.RDD的创建

  RDD的创建主要有二种方式:

  • 通过并行化创建(本地集合转分布式RDD)
    • sc.parallelize:本地集合转化分布式RDD
    • rdd.getNumPartitions():获取RDD分区数量,返回值是Int数字
    • rdd.collect():分布式RDD转化为本地集合
  • 读取外部数据源(读取文件)
    • sparkcontext.textFile(参数1,参数2):既可以读取本地数据,也可以读取HDFS数据
    • sparkcontext.wholeTextFiles:小文件读取专用
  • 并行化创建(本地集合转分布式RDD)
    API:parallelize

    rdd = sparkcontext.parallelize(参数1,参数2)
    # 参数1:集合对象即可,比如list
    # 参数2:分区数
    
    #!usr/bin/env python
    # -*- coding:utf-8 -*-
    
    # 导入pyspark的相关包
    from pyspark import SparkConf, SparkContext
    
    if __name__ == '__main__':
        # 1.构建SparkContext对象
        conf = SparkConf().setAppName("create rdd").setMaster("local[*]")
        sc = SparkContext(conf=conf)
    
        # TODO:演示通过并行化集合的方式创建RDD
        # 本地集合转分布式RDD
        rdd = sc.parallelize([1, 2, 3, 4, 5, 6, 7, 8, 9])
        # parallelize方法:在没有给定分区数的情况下,打印分区数(根据CPU核心数来定)
        print("默认分区数:", rdd.getNumPartitions())
    	
    	# 
        rdd = sc.parallelize([1, 2, 3, 4, 5, 6, 7, 8, 9], 3)
        print("分区数:", rdd.getNumPartitions())
    
        # collect方法:是将RDD(分布式对象)中每个分区的数据,都发送到Driver中,形成一个Python List对象
        # collect:分布式RDD转本地集合
        print("RDD的内容是:", rdd.collect())
    
    默认分区数: 2
    分区数: 3
    RDD的内容是: [1, 2, 3, 4, 5, 6, 7, 8, 9]
    
  • 读取外部数据源(读取文件)
    API:textFile

    sparkcontext.textFile(参数1,参数2)
    # 这个API既可以读取本地数据,也可以读取HDFS数据
    # 参数1,必填,文件路径支持本地文件,支持HDFS,也支持一些比如S3协议
    # 参数2,可选,表示最小分区数量
    # 注意:textFile一般除了你有很明确的指向性,一般情况下,我们不设置分区参数。参数2话语权不足,spark有自己的判断,在它允许的范围内,参数2有效果,超出spank允许的范围,参数2失效。
    
    #!usr/bin/env python
    # -*- coding:utf-8 -*-
    
    # 导入pyspark的相关包
    from pyspark import SparkConf, SparkContext
    
    if __name__ == '__main__':
        # 1.构建sparkcontext对象
        conf = SparkConf().setAppName('create rdd').setMaster('local[*]')
        sc = SparkContext(conf=conf)
    
        # TODO:读取外部数据源(读取本地文件)
        # 2.textFile()读取文件
        file_rdd1 = sc.textFile('../data/input/words.txt')
        print('默认读取分区数:', file_rdd1.getNumPartitions())
        print('file_rdd1内容:', file_rdd1.collect())
    
        # 加最小分区数参数的测试
        file_rdd2 = sc.textFile('../data/input/words.txt', 3)
        print('file_rdd2分区数:', file_rdd2.getNumPartitions())
    
        file_rdd3 = sc.textFile('../data/input/words.txt', 100)
        print('file_rdd3分区数:', file_rdd3.getNumPartitions())
    
        # TODO:读取外部数据源(读取HDFS文件)
        hdfs_rdd = sc.textFile('hdfs://node1:8020/test/input/wordcount.txt')
        print("hdfs_rdd 内容:", hdfs_rdd.collect())
    
    默认读取分区数: 2
    file_rdd1内容: ['hello spark', 'hello hadoop', 'hello flink']
    file_rdd2分区数: 4
    file_rdd3分区数: 38
    hdfs_rdd 内容: ['hadoop hello world hive', 'hive hadoop hello hadoop', 'hadoop hive hive']
    

    API:wholeTextFile

    sparkcontext.wholeTextFiles(参数1,参数2)
    # 读取文件的API,有个适用场景:适合读取一堆小文件
    #参数1,必填,文件路径支持本地文件支持HDFS也支持一些比如S3协议
    #参数2,可选,表示最小分区数量.
    #注意:参数2话语权不足,这个API分区数量最多也只能开到文件数量
    # 这个API偏向于少量分区读取数据。因为,这个API表明了自己是小文件读取专用,那么文件的数据很小分区很多,导致shuffle的几率更高。所以尽量少分区读取数据。
    
    #!usr/bin/env python
    # -*- coding:utf-8 -*-
    
    from pyspark import SparkConf, SparkContext
    
    if __name__ == '__main__':
        # 1.构建SparkContext对象
        conf = SparkConf().setAppName('create rdd').setMaster('local[*]')
        sc = SparkContext(conf=conf)
    
        # TODO:读取小文件
        file_rdd1 = sc.wholeTextFiles('../data/input/tiny_files')
        print(file_rdd1.collect())
        print(file_rdd1.map(lambda x: x[1]).collect())
    
    [('file:/tmp/pycharm_project_189/data/input/tiny_files/1.txt', 'hello spark\r\nhello hadoop\r\nhello flink'), ('file:/tmp/pycharm_project_189/data/input/tiny_files/2.txt', 'hello spark\r\nhello hadoop\r\nhello flink'), ('file:/tmp/pycharm_project_189/data/input/tiny_files/3.txt', 'hello spark\r\nhello hadoop\r\nhello flink'), ('file:/tmp/pycharm_project_189/data/input/tiny_files/4.txt', 'hello spark\r\nhello hadoop\r\nhello flink'), ('file:/tmp/pycharm_project_189/data/input/tiny_files/5.txt', 'hello spark\r\nhello hadoop\r\nhello flink')]
    ['hello spark\r\nhello hadoop\r\nhello flink', 'hello spark\r\nhello hadoop\r\nhello flink', 'hello spark\r\nhello hadoop\r\nhello flink', 'hello spark\r\nhello hadoop\r\nhello flink', 'hello spark\r\nhello hadoop\r\nhello flink']
    

3.RDD算子概念和分类

  • RDD算子的概念:

    算子:分布式对象API
    方法/函数:本地对象API

  • RDD算子分类:

    RDD算子分为两类:Transformation算子和Action算子

    • Transformation算子
      定义:RDD的算子,返回值仍然是RDD的算子,称之为转换算子
      特性:这类算子是懒加载的。如果没有action算子,Transformation算子是不工作的
    • Action算子
      定义:返回值不是RDD的就是Action算子

    对于这两类算子来说,Transformation算子相当于在构建执行计划,action是一个指令让这个执行计划开始工作。如果没有action,Transformation算子之间的迭代关系就是一个没有通电的流水线。只有action到来,这个数据处理的流水线才开始工作。

4.常用Transformation算子

4.1 转换算子——map
  • 功能
    map算子将RDD的数据一条条处理(处理逻辑:基于map算子中接受的函数),返回新的RDD

  • 语法

    rdd.map(func)
    # func:  f(T) -> U,传入参数并给予返回值
    # map算子作用在每一个分区上,
    
  • 实战

    """
    对于算子的接收函数来说,两种方法都可以
        - lambda表达式适用于一行代码就搞定的函数体
        - 如果多行代码,需要定义独立的方法
    """
    from pyspark import SparkConf, SparkContext
    
    if __name__ == '__main__':
        # 1.构建SparkContext对象
        conf = SparkConf().setAppName('create rdd').setMaster('local[*]')
        sc = SparkContext(conf=conf)
    
        # TODO:map算子——定义add函数
        rdd = sc.parallelize([1, 2, 3, 4, 5, 6], 3)
    
    
        # 定义方法,作为算子的传入函数体
        def add(data):
            return data * 10
    
    
        rdd1 = rdd.map(add)
        print(rdd1.collect())
    
        # TODO:map算子——定义lambda表达式写匿名函数
        rdd1 = rdd.map(lambda x: x * 10)
        print(rdd1.collect())
    
    [10, 20, 30, 40, 50, 60]
    [10, 20, 30, 40, 50, 60]
    
4.2 转换算子——flatMap
  • 功能
    对RDD执行map操作,然后进行解除嵌套操作。
    在这里插入图片描述

  • 实战

    #!usr/bin/env python
    # -*- coding:utf-8 -*-
    
    from pyspark import SparkConf, SparkContext
    
    if __name__ == '__main__':
        # 1.构建SparkContext对象
        conf = SparkConf().setAppName('create rdd').setMaster('local[*]')
        sc = SparkContext(conf=conf)
    
        # 2.并行化创建——本地集合转分布式RDD
        rdd = sc.parallelize(['hadoop spark hadoop', 'spark hadoop hadoop', 'hadoop flink spark'])
    
        # TODO:map操作,返回嵌套式结构
        rdd2 = rdd.map(lambda line: line.split(' '))
        print(rdd2.collect())
    
        # TODO:flatMap操作,执行完map操作后,然后解除嵌套
        rdd3 = rdd.flatMap(lambda line: line.split(' '))
        print(rdd3.collect())
    
    [['hadoop', 'spark', 'hadoop'], ['spark', 'hadoop', 'hadoop'], ['hadoop', 'flink', 'spark']]
    ['hadoop', 'spark', 'hadoop', 'spark', 'hadoop', 'hadoop', 'hadoop', 'flink', 'spark']
    
4.3 转换算子——reduceByKey
  • 功能:
    针对KV型RDD,自动按照key分组,然后根据你提供的聚合逻辑,完成组内数据(value)的聚合操作。简单来说,自动按key分组,按value聚合

  • 语法:

    rdd.reduceByKey(func)
    # func:(V, V) → V
    #接受2个传入参数(类型要一致),返回一个返回值,类型和传入要求—致
    

    以聚合函数:lambda a,b : a+b为例,其组内数据聚合逻辑为:
    在这里插入图片描述

  • 实战

    #!usr/bin/env python
    # -*- coding:utf-8 -*-
    
    from pyspark import SparkContext, SparkConf
    
    if __name__ == '__main__':
        # 1.构建SparkContext对象
        conf = SparkConf
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幼稚的人呐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值