Spark 广播变量&累加器

文章介绍了Spark中广播变量的使用,如何通过它来优化内存占用和网络传输,特别是在Executor中减少数据副本,提高性能。同时,文章也阐述了累加器在分布式计算中进行全局计数的功能,以及在map操作中的应用实例。通过案例展示了如何结合广播变量和累加器解决特定的数据处理问题。
摘要由CSDN通过智能技术生成

广播变量

场景描述:一份数据存在Driver中,但是每个Executor都需要一份。
常规模式下,Driver会给每个分区都发送一份数据。如果在Executor中存在多个分区的情况,那么一个Executor会获得多份数据。
Executor是进程,task是线程。分区位于线程中,那么在同一个Executor进程中,里面的线程是共享数据的。
所以理想情况下,我只给一个Executor发送数据即可,这样可以节约内存和网络IO。
在这里插入图片描述

解决方法: 广播变量
在设置广播变量之后,分区1获取数据,Driver会留下记录,Executor1获取了哪个广播变量。
当分区2找Driver获取数据时,Driver会先检查这个广播变量是否被这个Executor获取过,如果获取过就不会给分区2,并告知分区2这个广播变量已经被获取过,那么分区2就会回到Executor中找其他分区索要数据。

在这里插入图片描述

使用方式:

# 1. 将本地list标记称广播变量即可
broadcast = sc.broadcast(stu_info_list)

# 2. 使用广播变量,从broadcast对象取出本地list对象即可
value = broad.value

# 也就是 先放进去broadcast内部,然后从broadcast内部再取出来,中间传输的是broadcast这个对象了
# 只要中间传输的是broadcast对象,spark就会留意,只会给每个Executor发一份,而不是给每个分区发一份

代码示例:

# coding:utf8
import time

from pyspark import SparkConf, SparkContext
from pyspark.storagelevel import StorageLevel

if __name__ == '__main__':
    conf = SparkConf().setAppName("test").setMaster("local[*]")
    sc = SparkContext(conf=conf)

    stu_info_list = [(1, '张大仙', 11),
                     (2, '王晓晓', 13),
                     (3, '张甜甜', 11),
                     (4, '王大力', 11)]
    # 1. 将本地Python List对象标记为广播变量
    broadcast = sc.broadcast(stu_info_list)

    score_info_rdd = sc.parallelize([
        (1, '语文', 99),
        (2, '数学', 99),
        (3, '英语', 99),
        (4, '编程', 99),
        (1, '语文', 99),
        (2, '编程', 99),
        (3, '语文', 99),
        (4, '英语', 99),
        (1, '语文', 99),
        (3, '英语', 99),
        (2, '编程', 99)
    ])

    def map_func(data):
        id = data[0]
        name = ""
        # 匹配本地list和分布式rdd中的学生ID  匹配成功后 即可获得当前学生的姓名
        # 2. 在使用到本地集合对象的地方, 从广播变量中取出来用即可
        for stu_info in broadcast.value:
            stu_id = stu_info[0]
            if id == stu_id:
                name = stu_info[1]

        return (name, data[1], data[2])


    print(score_info_rdd.map(map_func).collect())

"""
场景: 本地集合对象 和 分布式集合对象(RDD) 进行关联的时候
需要将本地集合对象 封装为广播变量
可以节省:
1. 网络IO的次数
2. Executor的内存占用
"""

如果两个都是分布式集合对象,那么就不会造成内存浪费。但是两个RDD要结合使用,需要使用JOIN算子。
JOIN必然产生shuffle,可能造成性能降低。
分布式产生很多shuffle,降低性能:使用JOIN时,只会讲分量数据(需要的部分数据)传输。
而广播的本地list,是全量数据传输。
所以再本机数据不大的情况下,几千或者几万条数据,可能大小只有几MB,这样性能还比两个RDD做JOIN操作性能更好。
如果本地数据太大,例如几个GB那种,还是将本地数据存放到分布式集合对象中,使用JOIN算子关联
在这里插入图片描述

累加器

需求:想要对map算子计算中的数据,进行计数累加
得到全部数据计算完后的累加结果

分布式计算中的累加问题:
在这里插入图片描述
打印结果:
在这里插入图片描述

首先要明确非RDD代码由Driver执行
只是把count的值发送到分区,并不是指针,而且就算是指针也访问不到,不属于同一个服务器。连内存都不是同一块,所以指针没有任何用处。
在这里插入图片描述

解决方法:累加器
累加器再各个分区生效。都是访问的同一份数据。

代码演示:

# coding:utf8
import time

from pyspark import SparkConf, SparkContext
from pyspark.storagelevel import StorageLevel

if __name__ == '__main__':
    conf = SparkConf().setAppName("test").setMaster("local[*]")
    sc = SparkContext(conf=conf)

    rdd = sc.parallelize([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 2)

    # Spark提供的累加器变量, 参数是初始值
    acmlt = sc.accumulator(0)

    def map_func(data):
        global acmlt
        acmlt += 1
        # print(acmlt)

    rdd2 = rdd.map(map_func)
    rdd2.cache()  # acmlt = 10,添加缓存
    rdd2.collect() # action算子,执行之后rdd2就会销毁

    rdd3 = rdd2.map(lambda x:x) 
     # 如果不使用缓存,再次使用 rdd2,就会溯源,就又会走一次map算子,输出结果为20
    rdd3.collect()
    print(acmlt) # 添加缓存,输出为10 。 如果不加缓存,输出为20


综合案例

需求:
在这里插入图片描述

# coding:utf8
import time

from pyspark import SparkConf, SparkContext
from pyspark.storagelevel import StorageLevel
import re

if __name__ == '__main__':
    conf = SparkConf().setAppName("test").setMaster("local[*]")
    sc = SparkContext(conf=conf)

    # 1. 读取数据文件
    file_rdd = sc.textFile("../data/input/accumulator_broadcast_data.txt")

    # 特殊字符的list定义
    abnormal_char = [",", ".", "!", "#", "$", "%"]

    # 2. 将特殊字符list 包装成广播变量
    broadcast = sc.broadcast(abnormal_char)

    # 3. 对特殊字符出现次数做累加, 累加使用累加器最好
    acmlt = sc.accumulator(0)

    # 4. 数据处理, 先处理数据的空行, 在Python中有内容并且去除头尾空格,也就是返回True,  
    # 没有内容返回None,也就是返回False
    lines_rdd = file_rdd.filter(lambda line: line.strip())

    # 5. 去除前后的空格
    data_rdd = lines_rdd.map(lambda line: line.strip())

    # 6. 对数据进行切分, 按照正则表达式切分, 因为空格分隔符某些单词之间是两个或多个空格
    # 正则表达式 \s+ 表示 不确定多少个空格, 最少一个空格
    words_rdd = data_rdd.flatMap(lambda line: re.split("\s+", line))

    # 7. 当前words_rdd中有正常单词 也有特殊符号.
    # 现在需要过滤数据, 保留正常单词用于做单词计数, 在过滤 的过程中 对特殊符号做计数
    def filter_func(data):
        """过滤数据, 保留正常单词用于做单词计数, 在过滤 的过程中 对特殊符号做计数"""
        global acmlt
        # 取出广播变量中存储的特殊符号list
        abnormal_chars = broadcast.value
        if data in abnormal_chars:
            # 表示这个是 特殊字符
            acmlt += 1
            return False
        else:
            return True

    normal_words_rdd = words_rdd.filter(filter_func)
    # 8. 正常单词的单词计数逻辑
    result_rdd = normal_words_rdd.map(lambda x: (x, 1)).\
        reduceByKey(lambda a, b: a + b)

    print("正常单词计数结果: ", result_rdd.collect())
    print("特殊字符数量: ", acmlt)

总结

1、广播变量解决了什么问题
分布式集合RDD和本地集合进行关联使用的时候,降低内存占用以及减少网络IO传输,提高性能

2、累加器解决了什么问题
分布式代码执行中,进行全局累加

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值