spark sql优化:小表大表关联优化 & union替换or & broadcast join

本文通过一个具体的案例,展示了如何针对Spark SQL查询进行性能优化,包括调整大小表位置、使用UNION替换OR以及采用Cache+Broadcast的方式,最终将查询时间从18分钟缩短至20秒。

----原语句(运行18min)


    SELECT
            bb.ip
    FROM
            (
                    SELECT
                            ip                  ,
                            sum(click) click_num,
                            round(sum(click) / sum(imp), 4) user_click_rate
                    FROM
                            schema.srctable1
                    WHERE
                            date    = '20171020'
                            AND ip IS NOT NULL
                            AND imp > 0
                    GROUP BY
                            ip
            )
            bb
    LEFT OUTER JOIN
            (
                    SELECT
                            round(sum(click) / sum(imp), 4) avg_click_rate
                    FROM
                            schema.srctable1
                    WHERE
                            date = '20171020'
            )
            aa
    LEFT OUTER JOIN schema.dstable cc
    on
            cc.ip = bb.ip
    WHERE
            cc.ip is null
            AND
            (
                    bb.user_click_rate > aa.avg_click_rate * 3
                    AND click_num      > 500
            )
            OR
            (
                    click_num > 1000
            )


分析:

1、aa表存放的就是一个指标数据,1条记录,列为小表
2、bb表存放的是按ip聚合的明细数据,记录很多,列为大表
3、cc表用来过滤ip,数量也很小,列为过滤表,作用很小。
查看执行计划,发现bb与aa进行left outer join时,引发了shuffle过程,造成大量的磁盘及网络IO,影响性能。

解决策略

优化方案1:调整大小表位置,将小表放在左边后,提升至29s (该方案一直不太明白为啥会提升,执行计划里显示的也就是大小表位置调换下而已,跟之前的没其他区别)
优化方案2: 将 or 改成 union,提升至35s(各种调整,一直怀疑跟or有关系,后面调整成union其他不变,果真效率不一样;但方案1只是调整了下大小表顺序,并未调整其他,其效率同样提升很大;不太明白sparksql内部到底走了什么优化机制,后面继续研究);

优化方案3: 采用cache+broadcast方式,提升至20s(该方案将小表缓存至内存,进行map侧关联)

方案具体实施


----方案2:or 改成 union(运行35s)

I    select
            aa.ip
    from
            (
                    SELECT
                            bb.ip ip
                    FROM
                            (
                                    SELECT
                                            ip                  ,
                                            sum(click) click_num,
                                            round(sum(click) / sum(imp), 4)
                                            user_click_rate
                                    FROM
                                            schema.srctable1
                                    WHERE
                                            date    = '20171020'
                                            AND ip IS NOT NULL
                                            AND imp > 0
                                    GROUP BY
                                            ip
                            )
                            bb
                    LEFT OUTER JOIN
                            (
                                    SELECT
                                            round(sum(click) / sum(imp), 4)
                                            avg_click_rate
                                    FROM
                                            schema.srctable1
                                    WHERE
                                            date = '20171020'
                            )
                            aa
                    WHERE
                            (
                                    bb.user_click_rate > aa.avg_click_rate * 3
                                    AND click_num      > 20
                            )
                    
                    union
                    
                    SELECT
                            bb.ip ip
                    FROM
                            (
                                    SELECT
                                            ip                  ,
                                            sum(click) click_num,
                                            round(sum(click) / sum(imp), 4)
                                            user_click_rate
                                    FROM
                                            schema.srctable1
                                    WHERE
                                            date    = '20171020'
                                            AND ip IS NOT NULL
                                            AND imp > 0
                                    GROUP BY
                                            ip
                            )
                            bb
                    LEFT OUTER JOIN
                            (
                                    SELECT
                                            round(sum(click) / sum(imp), 4)
                                            avg_click_rate
                                    FROM
                                            schema.srctable1
                                    WHERE
                                            date = '20171020'
                            )
                            aa
                    WHERE
                            click_num > 40
            )
            aa
    LEFT OUTER JOIN schema.dstable cc
    on
            aa.ip = cc.ip
    where
            cc.ip is null  


-----cache+broadcast方式(20s)
原理:使用broadcast将会把小表分发到每台执行节点上,因此,关联操作都在本地完成,基本就取消了shuffle的过程,运行效率大幅度提高。
    cache table cta
    as
            SELECT
                    round(sum(click) / sum(imp), 4) avg_click_rate
            FROM
                    schema.srctable1
            WHERE
                    date = '20171020';
     INSERT into TABLE schema.dstable
     SELECT
                    bb.ip
     FROM
                    (
                            SELECT
                                    ip                  ,
                                    sum(click) click_num,
                                    round(sum(click) / sum(imp), 4) user_click_rate
                            FROM
                                    schema.srctable1
                            WHERE
                                    date    = '20171020'
                                    AND ip IS NOT NULL
                                    AND imp > 0
                            GROUP BY
                                    ip
                    )
                    bb
     LEFT OUTER JOIN cta aa
     LEFT OUTER JOIN schema.dstable cc
            on
                    cc.ip = bb.ip
     WHERE
                    cc.ip is null
                    AND
                    (
                            bb.user_click_rate > aa.avg_click_rate * 3
                            AND click_num      > 500
                    )
                    OR
                    (
                            click_num > 1000
                    )
 
注意:
cache 表不一定会被广播到Executor,执行map side join,还受另外一个参数:spark.sql.autoBroadcastJoinThreshold影响,该参数判断是否将该表广播;
spark.sql.autoBroadcastJoinThreshold参数默认值是10M,所以只有cache的表小于10M的才被广播到Executor上去执行map side join。 
<think>嗯,用户问的是关于在Spark中处理40亿数据量的优化方法。首先,我需要回忆一下处理数据时的常见优化策略,并结合Spark的特性来思考。40亿条数据,这个量级确实很,单机处理肯定不行,必须用分布式系统,而Spark作为常用的处理框架,优化点应该围绕资源管理、数据分区、缓存、算法选择等方面。 首先,资源调优是关键。用户需要合理配置executor的数量、内存和核心。比如,executor的内存不能太,否则会导致GC时间过长,但也不能太小,否则容易OOM。可能要考虑堆外内存的使用,以及动态资源分配,这样可以根据任务需求调整资源,避免浪费。 然后是数据分区。40亿数据如果分区不当,会导致数据倾斜,某些task处理的数据量远于其他,拖慢整个作业。可能需要调整分区数,比如默认是200,但数据量增时可能需要增加到几千甚至更多,确保每个分区的数据量适中,比如每个分区128MB左右。同时,处理数据倾斜的方法,比如加盐、两阶段聚合,可能需要详细说明。 接着是持久化与缓存。对于需要多次使用的RDD或DataFrame,使用cache或persist可以避免重复计算。但要注意存储级别的选择,比如MEMORY_AND_DISK,当内存不足时溢写到磁盘。此外,检查点机制可以切断血缘关系,减少恢复时间。 代码优化方面,避免使用低效的算子,比如groupByKey,改用reduceByKey或aggregateByKey,因为后者会在map端进行combine,减少shuffle的数据量。同时,mapPartitions可能比map更高效,特别是在需要创建连接时,每个分区只创建一次连接。 广播变量和累加器的使用也很重要。join小表时,可以用广播变量把小表分发到各个节点,避免shuffle。累加器适合做全局统计,比如计数或求和,减少driver和executor之间的通信开销。 数据格式的选择,比如Parquet或ORC,列式存储有更好的压缩率和查询性能。同时,数据压缩可以减少IO和网络传输,比如使用snappy或lz4,但要注意压缩和解压的开销是否值得。 Spark SQL优化,比如启用自适应查询执行(AQE),自动调整执行计划,动态合并shuffle分区,处理倾斜join。还有Catalyst优化器的利用,比如谓词下推和列剪枝,减少处理的数据量。 硬件层面的优化可能需要增加节点数量,使用SSD提升IO性能,网络升级到万兆。监控工具如Spark UI和Ganglia,帮助定位瓶颈,调整配置。 另外,数据倾斜的具体处理办法可能需要更详细的例子,比如在join时遇到倾斜key,可以拆分处理或者加随机前缀。还有shuffle的优化,调整缓冲区小,压缩算法等。 最后,可能需要结合具体的业务场景,比如是ETL、机器学习还是实时处理,不同的场景优化重点可能不同。用户可能有实际案例中的痛点,比如某个stage特别慢,需要针对性解决。 总之,需要从资源配置、数据分区、代码优化、Shuffle管理、数据格式、硬件和监控等多个方面综合考虑,逐步调整和测试,找到最适合当前集群和作业的优化策略。</think>在 Spark 中处理 40 亿级别数据量时,需通过多维度优化策略提升性能。以下是分步骤的优化方案(附关键代码示例): 一、资源调优 ```bash # 提交任务时典型参数配置示例 spark-submit \ --executor-memory 16G \ # 每个Executor内存 --executor-cores 4 \ # 每个Executor核心数 --num-executors 50 \ # Executor总数 --conf spark.dynamicAllocation.enabled=true \ # 启用动态资源分配 --conf spark.memory.fraction=0.8 \ # JVM堆内存分配给Spark的比例 ``` 二、数据分区优化 ```scala // 调整分区数(假设原始数据400GB) val df = spark.read.parquet("s3://input/") .repartition(4000) // 按4000分区(每个分区约100MB) // 处理数据倾斜示例(加盐扩容) val skewedDF = df.withColumn("salt", (rand()*100).cast("int")) val aggregated = skewedDF.groupBy("key", "salt") .agg(sum("value").as("partial_sum")) .groupBy("key") .agg(sum("partial_sum").as("total_sum")) ``` 三、Shuffle优化 ```scala // 调整Shuffle参数 spark.conf.set("spark.sql.shuffle.partitions", "4000") // 默认200改为4000 spark.conf.set("spark.shuffle.file.buffer", "1MB") // Shuffle写缓冲区 spark.conf.set("spark.reducer.maxSizeInFlight", "128MB")// 读取缓冲区 ``` 四、执行计划优化 ```scala // 启用AQE(Spark 3.0+) spark.conf.set("spark.sql.adaptive.enabled", true) spark.conf.set("spark.sql.adaptive.coalescePartitions.enabled", true) spark.conf.set("spark.sql.adaptive.skewJoin.enabled", true) // 广播Join优化小表<10MB时自动生效) val smallDF = spark.read.parquet("s3://small_table/") val bigDF = spark.read.parquet("s3://big_table/") bigDF.join(broadcast(smallDF), "key") // 强制广播 ``` 五、数据存储优化 ```scala // 写入优化(使用Z-Ordering) df.write .option("maxRecordsPerFile", 1000000) // 控制文件小 .bucketBy(32, "key") // 分桶存储 .sortBy("timestamp") // 排序写入 .format("parquet") .save("s3://optimized-output/") ``` 六、代码优化技巧 ```scala // 使用高效算子替代低效操作 // 错误示例(全量shuffle) df.groupBy("key").agg(collect_list("value")) // 优化方案(预聚合+控制输出) import org.apache.spark.sql.expressions.Window val winSpec = Window.partitionBy("key").orderBy("timestamp") df.withColumn("rank", row_number().over(winSpec)) .filter($"rank" <= 1000) // 每个key保留前1000条 .groupBy("key").agg(collect_list("value")) ``` 七、监控与诊断 1. 查看Stage执行情况: ```bash # 通过Spark UI分析: # - 各Stage耗时 # - Shuffle读写量 # - Task数据倾斜情况(75th/median差异) ``` 2. 检查执行计划: ```scala df.explain("formatted") // 查看物理计划 ``` 典型优化效果对比: | 优化项 | 优化前耗时 | 优化后耗时 | 资源消耗下降 | |----------------------|------------|------------|--------------| | 默认分区(200) | 6小时 | - | - | | 调整到4000分区 | - | 4.2小时 | 30% | | 开启AQE | 4.2小时 | 3.1小时 | 25% | | 广播Join优化 | 3.1小时 | 2.4小时 | 40% | | Z-Order存储 | 2.4小时 | 1.8小时 | 35% | 注意事项: 1. 每次修改配置后需进行小规模验证(可采样1%数据测试) 2. 使用Spark 3.x版本可获得更好的AQE优化效果 3. 对于超数据量,建议采用增量处理模式: ```scala // 按时间范围分批处理 val dailyDF = spark.read.parquet("s3://data/") .filter($"event_date".between(startDate, endDate)) ``` 遇到数据倾斜时的应急方案: ```scala // 极端倾斜处理(分离倾斜key) val skewKeys = Seq("key1", "key2") // 已知倾斜key val commonDF = df.filter(!$"key".isin(skewKeys:_*)) val skewDF = df.filter($"key".isin(skewKeys:_*)) // 分别处理 val resultCommon = commonDF.groupBy("key").agg(...) val resultSkew = skewDF.repartition(100, $"key") // 单独扩分区 .groupBy("key").agg(...) // 合并结果 resultCommon.union(resultSkew) ``` 这些优化策略需要根据实际集群配置和数据特征进行调整,建议通过以下步骤实施: 1. 使用Spark UI识别瓶颈阶段 2. 针对性调整相关参数(每次只改一个变量) 3. 记录基准测试结果 4. 对ETL流水线进行分阶段优化 5. 最终实施组合优化方案
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值