PySpark大数据分析(5):数据分区

分区的意义

在Spark这类分布式程序中,通信的开销非常大。控制数据分区的意义就在于,通过合理的数据分布减少网络传输从而提升性能。对数据进行分区主要用于优化基于键的操作。比如我们整理出要给用户推荐的召回结果,在推荐之前先用其最近浏览结果进行一次过滤:

from pyspark import SparkConf, SparkContext


conf = SparkConf().setMaster('local').setAppName('MyApp')
sc = SparkContext(conf=conf)

user_recall = sc.parallelize([
    ('uid001', ('item001', 'item003', 'item006', 'item009', 'item012')),
    ('uid002', ('item003', 'item004', 'item007', 'item011', 'item015')),
    ('uid003', ('item002', 'item005', 'item008', 'item010', 'item011')),
    ('uid004', ('item001', 'item004', 'item007', 'item012', 'item014')),
    ('uid005', ('item005', 'item008', 'item009', 'item014', 'item015'))
])

latest_impressions = sc.parallelize([
    ('uid001', ('item006', 'item003')),
    ('uid004', ('item001',)),
    ('uid002', ('item009', 'item003'))
])

def remove_impre(rdd):
    uid, rec_impre = rdd
    rec_list, impre_list = rec_impre
    if impre_list is None:
        return uid, rec_list
    else:
        filtered = []
        for item in rec_list:
            if item not in impre_list:
                filtered.append(item)
        return uid, tuple(filtered)

def filter_impressions():
    joined = user_recall.leftOuterJoin(latest_impressions)
    return joined.map(remove_impre)

print(filter_impressions().collectAsMap())
'''
{'uid001': ('item001', 'item009', 'item012'),
 'uid002': ('item004', 'item007', 'item011', 'item015'),
 'uid003': ('item002', 'item005', 'item008', 'item010', 'item011'),
 'uid004': ('item004', 'item007', 'item012', 'item014'),
 'uid005': ('item005', 'item008', 'item009', 'item014', 'item015')}
'''

上面的代码可以进行曝光过滤,但是考虑到latest_impressions需要实时更新,这个过滤操作可能会被经常调用,而上面的代码每次都会执行join()操作,导致代码效率很低。

实际工作中,存储全量用户的user_recall表要比一直更新的latest_impressions表大很多,并且没有那么频繁的更新。一种比较常见的解决方式是,调用partitionBy()函数,先将user_recall表进行分区,再进行持久化。这样这张user_recall表就不需要在每次调用join()的时候进行数据混洗了。

user_recall = sc.parallelize([
    ('uid001', ('item001', 'item003', 'item006', 'item009', 'item012')),
    ('uid002', ('item003', 'item004', 'item007', 'item011', 'item015')),
    ('uid003', ('item002', 'item005', 'item008', 'item010', 'item011')),
    ('uid004', ('item001', 'item004', 'item007', 'item012', 'item014')),
    ('uid005', ('item005', 'item008', 'item009', 'item014', 'item015'))
]).partitionBy(numPartitions=5).persist()

为user_recall的键指定分区后,每次使用user_recall的键时,Spark都能知道它的键是根据键的哈希值进行过分区的。这样,当user_recall调用join()以连接latest_impressions时,Spark仅会latest_impressions进行数据混洗,然后将特定的键发送到对应的user_recall所在的机器上,从而降低网络传输数据的开销。

这里需要注意两点,第一是partitionBy()是一个转化操作,它返回的是新的RDD,而不是去修改原先的RDD。因此务必将partitionBy()的结果持久化,并保存为一个新的RDD。第二是参数numPartitions表示分区数,同时也会控制这个RDD后面操作的并行任务数,因此这个值一般和集群中的总核心数一致。

PySpark也支持自定义的分区方式,只要将自定义的哈希函数作为参数传递给partitionBy()就可以了。比如仅根据uid的最后两位进行哈希:

def uid_hash(uid):
    return hash(uid[-2:])

user_recall = user_recall.partitionBy(numPartitions=2, partitionFunc=uid_hash).persist()

保留分区信息

在Spark的一些转化操作中,如果该操作可以获取到父RDD的分区信息,那这些操作会将这些信息设定在返回的RDD结果中。同时,很多操作也可以利用已知的RDD分区信息,比如sortByKey()和groupByKey(),他们分别生成范围分区的RDD和哈希分区的RDD。但是类似map()这样可以修改键的操作,它返回的RDD并不会记录父RDD的分区信息。Spark提供了替代方案mapValues(),可以保证仅操作值而不更改键,因此结果可以保留父RDD的分区信息。同样的还有flatMapValues()。

x = sc.parallelize([("a", ["apple", "banana", "lemon"]), ("b", ["grapes"])])
result = x.mapValues(lambda x: len(x)).collect()
print(result)
# [('a', 3), ('b', 1)]

一般情况下,一个pair RDD的操作结果的分区,取决于父RDD的分区方式,默认为哈希分区,且分区数也一致。如果两个父RDD都设置了自己的分区方式,那结果会按照第一个父RDD的方式进行分区。

上一篇:PySpark大数据分析(4):键值对操作
下一篇:PySpark大数据分析(6):数据读写

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Pyspark是一个开源的大数据处理框架,用于分布式数据处理和分析。以下是Pyspark常用操作的一些示例: 1. 数据读取和写入:使用Pyspark可以从各种数据源中读取数据,如Hadoop分布式文件系统(HDFS)、关系型数据库或CSV文件等。示例代码如下: ```python # 从CSV文件中读取数据 df = spark.read.csv("data.csv") # 将数据写入HDFS df.write.format("csv").mode("overwrite").save("hdfs://path/to/save") ``` 2. 数据转换:Pyspark提供了丰富的数据转换操作,如选择特定列、添加新列、过滤数据等。示例代码如下: ```python # 选择特定列 df.select("column1", "column2") # 添加新列 df.withColumn("new_column", df.column1 + df.column2) # 过滤数据 df.filter(df.column1 > 10) ``` 3. 聚合操作:Pyspark支持各种聚合操作,如求和、平均、最大值、最小值等。示例代码如下: ```python # 求和 df.groupBy("column1").sum("column2") # 平均值 df.groupBy("column1").avg("column2") # 最大值 df.groupBy("column1").max("column2") # 最小值 df.groupBy("column1").min("column2") ``` 4. 排序和分区Pyspark可以对数据进行排序和分区,以便更好地组织和处理数据。示例代码如下: ```python # 按特定列排序 df.orderBy("column1") # 按多个列排序 df.orderBy("column1", "column2") # 按特定列分区 df.repartition("column1") # 按多个列分区 df.repartition("column1", "column2") ``` 以上只是Pyspark常用操作的一些示例,Pyspark还提供了许多其他功能,如窗口函数、连接操作等,可以根据具体需求进行学习和应用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值