Spark : 多线程提交优化多Job任务

本文探讨了Spark在批量读取Hudi文件时出现的文件丢失问题,并提出了一种通过多线程分别读取各个文件夹来提高效率的方法。

介绍

在日常业务中,spark常见的就是通过路径通配符*,{}等方式一次读取多个文件,一次批处理将这些文件做一个大job写入Hive或者ODPS,笔者最近在用Spark读取Hudi的文件时候发现了一个诡异的文件丢失Bug:

一次读入所有文件夹会有部分文件夹丢失,一开始怀疑是这部分文件夹本身有损坏,但是用spark单独读取该文件夹的时候发现数据又不会丢失.

既然一次job会丢数据,那么不妨按文件夹拆分job,每个job执行单个任务,常见就是for循环去遍历所有文件夹挨个执行,但是效率过低需要

六个小时,在资源不变的情况下用多线程提交,成功把时间缩短到了一个小时.与全文件读取效率相当.

code

import java.util.concurrent.Executors
import scala.collection.JavaConverters.asScalaSetConverter
import scala.concurrent.duration.{Duration, HOURS}
import scala.concurrent.{Await, ExecutionContext, ExecutionContextExecutorService, Future}


def runJobDirByDir(conf:Config,prefix:String):Unit = {
	assert(prefix.startswith("oss://"),"a legal path should starts with [oss://]")
	//spark framework多个类要用到spark,所以用了ThreadLocal共享spark环境
	val spark = EnvUtils.take 
 	// 自己写的oss文件系统工具,作用是列举文件夹
	val ofs = new OssSystemUtil(conf)
	val projects = ofs.listDir(prefix)
	val pool = Executors.newFixedThreadPool(5)
	implicit val xc:ExecutionContextExecutorService = ExecutionContext.fromExecutorService(pool)
	//因为子线程的ThreadLocal里面没有sparkEnv,所以在这传进去
	val tasks = projects.map(x=> runFutureTask(spark,x)) 
	Await.result(Future.sequence(tasks.toSeq),Duration(5,MINUTE))
}

def runFutureTask(spark:SparkContext, dir:String)(implicit xc: ExecutionContext)
	:Future[Unit]= Future{
    //TODO run single dir 
}
### 单个 SparkContext 同时运行个作业 在 Apache Spark 的设计中,一个 `SparkContext` 实例在同一时间只能执行一个作业。然而,在实际应用场景下,可以通过一些策略让看似个作业并行运行于同一 `SparkContext` 下。 #### 使用线程池机制 通过创建多线程或者利用现有的线程框架(比如 Java 或 Python 自带的并发库),可以模拟出同时向同一个 `SparkContext` 提交不同任务的效果。需要注意的是这种方式并不是真正意义上的并行化操作,而是基于事件驱动模型的任务切换[^1]。 ```python from concurrent.futures import ThreadPoolExecutor import threading def run_job(job_id): # 假设这里定义了一个简单的 spark job 函数 result = sc.parallelize([job_id]*10).count() print(f'Job {job_id} finished with count={result}') with ThreadPoolExecutor(max_workers=5) as executor: futures = [executor.submit(run_job, i) for i in range(5)] ``` #### 调整配置参数 适当调整某些与调度有关的参数可以帮助提高效率,使得更任务能够更快完成从而达到近似并行的目的。例如设置合理的 shuffle partitions 数量以及内存分配比例等[^4]: - `spark.sql.shuffle.partitions`: 控制 Shuffle 操作产生的分区数目,默认值通常较小,增加此数值可以让更的计算资源被有效利用起来; - `spark.executor.memoryOverhead`, `spark.driver.memoryOverhead`: 给 Executor 和 Driver 设置额外的堆外内存空间,防止因 OOM 错误而导致任务失败; #### 利用动态资源分配功能 启用 Dynamic Resource Allocation 功能后,当集群中有空闲资源可用时,Spark 应用会自动请求这些资源来加速正在等待被执行任务队列中的项。这有助于更好地平衡负载并在一定程度上实现了资源共享下的“伪并行”。 ```properties spark.dynamicAllocation.enabled=true spark.shuffle.service.enabled=true ``` #### 注意事项 尽管上述方法可以在某种程度上提升性能表现,但由于共享相同的 JVM 进程及其上下文环境,因此仍需谨慎对待可能引发的竞争条件问题,如文件句柄争抢、网络连接超限等情况的发生。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值