spark小文件处理
- 一、问题
- 二、解决思路
- 2.1 spark 计数方式
- 2.2 计数部分代码实现
- 2.3 性能影响评估
- 三、总结
一、问题
某个需求流程处理在上传s3阶段会使用spark 计算写入的数据,但是由于spark写入时是使用的默认分区200,虽然部分数据进行了分区数的处理,但是分区数效果不好。还是会存在几G或者更小十几M每个文件的情况,希望的是有一个通用的处理方式。
二、解决思路
使用spark 数据缓存,再通过去拿执行计划的缓存大小计算spark写入时所需的分区数!
2.1 spark 计数方式
由于希望有一个通用的处理方式,所以直接采用侵入spark 代码的方式进行处理
大致执行逻辑:
- 缓存spark df
- 执行计数
- 拿取执行计划缓存的df 大小
- 根据大小计算spark 合适的分区数
可能发生的问题,由于会侵入spark代码,所以会影响spark 处理性能,后续会针对不同数据情况分别查看spark执行的性能情况。
2.2 计数部分代码实现
from pyspark.sql import DataFrame as SparkDataFrame
def is_empty(df: SparkDataFrame):
df_result = None
try:
df_result = df.head()
except:
logging.warning("df.head() 存在异常!")
if df_result:
return False
else:
return True
def get_partition_num(df: SparkDataFrame, split_size=128, ra=1):
'''
df: spark 需要写入的DataFrame
split_size: 文件大小,hdfs块大小 128M
ra: 压缩比率
return 分区数
'''
if not is_empty(df):
df.cache()
df.count()
catalyst_plan = df._jdf.queryExecution().logical()
df_size_bytes = spark._jsparkSession.sessionState().executePlan(
catalyst_plan).optimizedPlan().stats().sizeInBytes()
df.unpersist()
return (df_size_bytes // 1024 // 1024 // split_size // ra) + 1
else:
return 40
df = spark.sql(exe_sql)
partition_file_num = get_partition_num(df)
spark.sql(f'''
INSERT OVERWRITE table {self.result_table}
SELECT
/*+ REPARTITION({partition_file_num}) */
*
FROM result_all
''')
2.3 性能影响评估
选了一些数据,进行spark执行性能评估,执行逻辑忽略了,直接上结果。
数据1:KB级别
未改动 | 改动后 | |
产出文件数 | 200 | 1 |
文件大小 | 100K左右 | 11M左右 |
执行时间 | 4Min | 4Min |
数据1:MB级别
未改动 | 改动后 | |
产出文件数 | 200 | 2 |
文件大小 | 1M左右 | 100M左右 |
执行时间 | 4Min | 3.5Min |
数据1:MB级别
未改动 | 改动后 | |
产出文件数 | 200 | 48 |
文件大小 | 1M左右 | 128M左右 |
执行时间 | 10Min | 8Min |
数据1:MB级别
未改动 | 改动后 | |
产出文件数 | 200 | 312 |
文件大小 | 200M左右左右 | 128M左右 |
执行时间 | 18Min | 18Min |
数据1:GB级别
未改动 | 改动后 | |
产出文件数 | 200 | 1129 |
文件大小 | 1.3G左右左右 | 128M左右 |
执行时间 | 30Min | 36Min |
三、总结
当批次数据量越大,count 计数的影响越明显,导致数据处理越慢。数据量小时,反而对执行有一定的促进作用,或者说几乎没有负面影响!