Spark Datafusion Comet 向量化Rust Native--Native算子(CometNativeExec)从JVM中获取RecordBatch数据

逆向工程实战:手把手教你破解药监局网站的动态数据加载机制(Python+Chrome开发者工具) 本文详细介绍了如何利用Python和Chrome开发者工具逆向破解药监局网站的动态数据加载机制。通过分析Ajax请求、解析JSON响应数据以及处理动态参数,帮助开发者高效采集动态加载内容,适用于爬虫开发与数据分析场景。 阅读详情

背景

Apache Datafusion Comet 是苹果公司开源的加速Spark运行的向量化项目。
本项目采用了 Spark插件化 + Protobuf + Arrow + DataFusion 架构形式
其中

  • Spark插件是 利用 SparkPlugin 插件,其中分为 DriverPlugin 和 ExecutorPlugin ,这两个插件在driver和 Executor启动的时候就会调用
  • Protobuf 是用来序列化 spark对应的表达式以及计划,用来传递给 native 引擎去执行,利用了 体积小,速度快的特性
  • Arrow 是用来 spark 和 native 引擎进行高效的数据交换(native执行的结果或者spark执行的数据结果),主要在JNI中利用Arrow IPC 列式存储以及零拷贝等特点进行进程间数据交换
  • DataFusion 主要是利用Rust native以及Arrow内存格式实现的向量化执行引擎,Spark中主要offload对应的算子到该引擎中去执行

本文基于 datafusion comet 截止到2026年1月13号的main分支的最新代码(对应的commit为 eef5f28a0727d9aef043fa2b87d6747ff68b827a)
主要分析Rust Native的Spark Datafusion Comet 向量化Rust Native–执行Datafusion计划中涉及到的获取RecordBatch数据细节实现

执行计划planner.create_plan,拉取数据

这里的方法会返回scans, root_op,如下:

let (scans, root_op) = planner.create_plan(
                    &exec_context.spark_plan,
                    &mut exec_context.input_sources.clone(),
                    exec_context.partition_count,
                )?;

从JVM传过来的参数可知,该input_sources 是所有该物理计划的输入,该方法会把 protobuf Operator一一映射为DataFusion physical plan,这里以Scan为例子(其他算子依次类推):

 OpStruct::Scan(scan) => {
   let data_types = scan.fields.iter().map(to_arrow_datatype).collect_vec();

   // If it is not test execution context for unit test, we should have at least one
   // input source
   if self.exec_context_id != TEST_EXEC_CONTEXT_ID && inputs.is_empty() {
       return Err(GeneralError("No input for scan".to_string()));
   }

   // Consumes the first input source for the scan
   let input_source =
       if self.exec_context_id == TEST_EXEC_CONTEXT_ID && inputs.is_empty() {
           // For unit test, we will set input batch to scan directly by `set_input_batch`.
           None
       } else {
           Some(inputs.remove(0))
       };

   // The `ScanExec` operator will take actual arrays from Spark during execution
   let scan = ScanExec::new(
       self.exec_context_id,
       input_source,
       &scan.source,
       data_types,
       scan.arrow_ffi_safe,
   )?;

   Ok((
       vec![scan.clone()],
       Arc::new(SparkPlan::new(spark_plan.plan_id, Arc::new(scan), vec![])),
   )) }
  • to_arrow_datatype依次映射数据类型到dataFusion对应的类型
  • inputs.remove(0)选择第一个输入的RDD为ScanExec和Scans
    返回一个(scans,ScanExec)元组类型

注意这里的 ScanExec实现了get_next_batch以及ExecutionPlan(来自DataFusion的物理计划)的execute方法:

pub fn get_next_batch(&mut self) -> Result<(), CometError> {
        if self.input_source.is_none() {
            // This is a unit test. We don't need to call JNI.
            return Ok(());
        }
        let mut timer = self.baseline_metrics.elapsed_compute().timer();

        let mut current_batch = self.batch.try_lock().unwrap();
        if current_batch.is_none() {
            let next_batch = ScanExec::get_next(
                self.exec_context_id,
                self.input_source.as_ref().unwrap().as_obj(),
                self.data_types.len(),
                self.arrow_ffi_safe,
            )?;
            *current_batch = Some(next_batch);
        }

        timer.stop();

        Ok(())
    }
...
fn execute(
        &self,
        partition: usize,
        _: Arc<TaskContext>,
    ) -> datafusion::common::Result<SendableRecordBatchStream> {
        Ok(Box::pin(ScanStream::new(
            self.clone(),
            self.schema(),
            partition,
            self.baseline_metrics.clone(),
        )))
    }

注意:这两个方法也是相互配合使用,execute获取对应的RecordBatch Stream数据(loop循环判断调用),get_next_batch会填充RecordBatch Stream数据
并且DataFusion物理计划execute方法用到的RecordBatch数据是通过后续JNI调用JVM的方法获取到的
这 execute返回的ScanStream类型的对象中的build_record_batch方法,会使用Arrow中cast_with_options进行字典解码,组装成native_plan.execute返回的RecordBatch结果,具体的可以看Function cast_with_options Copy item path

而这里的get_next_batch方法将会被pull_input_batches方法调用,这个方法调用JNI从JVM端获取数据:

fn pull_input_batches(exec_context: &mut ExecutionContext) -> Result<(), CometError> {
    exec_context.scans.iter_mut().try_for_each(|scan| {
        scan.get_next_batch()?;
        Ok::<(), CometError>(())
    })
}
...
/// Pull next input batch from JVM.
 pub fn get_next_batch(&mut self) -> Result<(), CometError> {
     if self.input_source.is_none() {
         // This is a unit test. We don't need to call JNI.
         return Ok(());
     }
     let mut timer = self.baseline_metrics.elapsed_compute().timer();

     let mut current_batch = self.batch.try_lock().unwrap();
     if current_batch.is_none() {
         let next_batch = ScanExec::get_next(
             self.exec_context_id,
             self.input_source.as_ref().unwrap().as_obj(),
             self.data_types.len(),
             self.arrow_ffi_safe,
         )?;
         *current_batch = Some(next_batch);
     }

     timer.stop();

     Ok(())
 }
  • timer为计算此次从JVM中拉取数据的耗时
  • 调用ScanExec::get_next方法获取对应的批次数据
    /// Invokes JNI call to get next batch.
     fn get_next(
         exec_context_id: i64,
         iter: &JObject,
         num_cols: usize,
         arrow_ffi_safe: bool,
     ) -> Result<InputBatch, CometError> {
         ...
         let mut env = JVMClasses::get_env()?;
    
         let num_rows: i32 = unsafe {
             jni_call!(&mut env,
         comet_batch_iterator(iter).has_next() -> i32)?
         };
    
         if num_rows == -1 {
             return Ok(InputBatch::EOF);
         }
    
         // Check for selection vectors and get selection indices if needed from
         // JVM via FFI
         // Selection vectors can be provided by, for instance, Iceberg to
         // remove rows that have been deleted.
         let selection_indices_arrays = Self::get_selection_indices(&mut env, iter, num_cols)?;
    
         // fetch batch data from JVM via FFI
         let (num_rows, array_addrs, schema_addrs) =
             Self::allocate_and_fetch_batch(&mut env, iter, num_cols)?;
    
         let mut inputs: Vec<ArrayRef> = Vec::with_capacity(num_cols);
    
         // Process each column
         for i in 0..num_cols {
             let array_ptr = array_addrs[i];
             let schema_ptr = schema_addrs[i];
             let array_data = ArrayData::from_spark((array_ptr, schema_ptr))?;
    
             // TODO: validate array input data
             // array_data.validate_full()?;
    
             let array = make_array(array_data);
    
             // Apply selection if selection vectors exist (applies to all columns)
             let array = if let Some(ref selection_arrays) = selection_indices_arrays {
                 let indices = &selection_arrays[i];
                 // Apply the selection using Arrow's take kernel
                 match take(&*array, &**indices, None) {
                     Ok(selected_array) => selected_array,
                     Err(e) => {
                         return Err(CometError::from(ExecutionError::ArrowError(format!(
                             "Failed to apply selection for column {i}: {e}",
                         ))));
                     }
                 }
             } else {
                 array
             };
    
             let array = if arrow_ffi_safe {
                 // ownership of this array has been transferred to native
                 // but we still need to unpack dictionary arrays
                 copy_or_unpack_array(&array, &CopyMode::UnpackOrClone)?
             } else {
                 // it is necessary to copy the array because the contents may be
                 // overwritten on the JVM side in the future
                 copy_array(&array)
             };
    
             inputs.push(array);
    
             // Drop the Arcs to avoid memory leak
             unsafe {
                 Rc::from_raw(array_ptr as *const FFI_ArrowArray);
                 Rc::from_raw(schema_ptr as *const FFI_ArrowSchema);
             }
         }
    
         // If selection was applied, determine the actual row count from the selected arrays
         let actual_num_rows = if let Some(ref selection_arrays) = selection_indices_arrays {
             if !selection_arrays.is_empty() {
                 // Use the length of the first selection array as the actual row count
                 selection_arrays[0].len()
             } else {
                 num_rows as usize
             }
         } else {
             num_rows as usize
         };
         Ok(InputBatch::new(inputs, Some(actual_num_rows)))
     }
    
    
    • JVMClasses::get_env()
      得到JavaVM对象,并调用attach_current_thread方法将当前Rust线程关联到JVM
    • 通过JNI调用对应的 Java CometBatchIterator hasNext方法,获取下一批返回的数据行数
      其中,有宏jni_call的定义,如下:
       macro_rules! jni_call {
          ($env:expr, $clsname:ident($obj:expr).$method:ident($($args:expr),* $(,)?) -> $ret:ty) => {{
              let method_id = paste::paste! {
                  $crate::jvm_bridge::JVMClasses::get().[<$clsname>].[<method_ $method>]
              };
              let ret_type = paste::paste! {
                  $crate::jvm_bridge::JVMClasses::get().[<$clsname>].[<method_ $method _ret>]
              }.clone();
              let args = $crate::jvm_bridge::jvalues!($($args,)*);
      
              // Call the JVM method and obtain the returned value
              let ret = $env.call_method_unchecked($obj, method_id, ret_type, args);
      
              // Check if JVM has thrown any exception, and handle it if so.
              let result = if let Some(exception) = $crate::jvm_bridge::check_exception($env)? {
                  Err(exception.into())
              } else {
                  $crate::jvm_bridge::jni_map_error!($env, ret)
              };
      
              result.and_then(|result| $crate::jvm_bridge::jni_map_error!($env, <$ret>::try_from(result)))
          }}
       }
      
      这里还会用到paste::paste!宏,该宏使用说明见这里,主要是它允许在 [<…>] 内的标识符被拼接成单个标识符
      该此调用的Java方法都被提前封装在JVMClasses中
    • 如果返回的行数为-1,则说明已经没有数据可以读取了,直接返回InputBatch::EOF
    • get_selection_indices 通过JNI获取到 selection indices,这里暂且跳过,后续会说明
    • allocate_and_fetch_batch通过 Arrow FFI 结构以及JNI获取到批次数据
    • 对于每一列应用selection_indices_arrays来获取真正的数据,这个后续会说明
  • 赋值当前的批次数据给ScanExec.batch作为给exec_context.stream中的最终数据返回给JVM端
    这里的以ScanExec计划为例子(该execute方法返回的是ScanStream):
     impl Stream for ScanStream<'_> {
     type Item = DataFusionResult<RecordBatch>;
    
     fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
         let mut timer = self.baseline_metrics.elapsed_compute().timer();
         let mut scan_batch: std::sync::MutexGuard<'_, Option<InputBatch>> = self.scan.batch.try_lock().unwrap();
         let input_batch = &*scan_batch;
         let input_batch = if let Some(batch) = input_batch {
             batch
         } else {
             timer.stop();
             return Poll::Pending;
         };
         let result = match input_batch {
             InputBatch::EOF => Poll::Ready(None),
             InputBatch::Batch(columns, num_rows) => {
                 self.baseline_metrics.record_output(*num_rows);
                 let maybe_batch = self.build_record_batch(columns, *num_rows);
                 Poll::Ready(Some(maybe_batch))
             }
         };
         *scan_batch = None;
         timer.stop();
         result
      }
     }
    
    • 记录本次拉取所有批次数据的耗时

       let mut timer = self.baseline_metrics.elapsed_compute().timer();
      
    • 初始的 batch值为None,所以根据

        let input_batch = &*scan_batch;
         let input_batch = if let Some(batch) = input_batch {
             batch
         } else {
             timer.stop();
             return Poll::Pending;
         };
      

      这里判断返回Poll::Pending,所以在主流程中会继续调用pull_input_batches拉取数据

    • 如果返回的是InputBatch::EOF这表明数据已经处理完了,则返回Poll::Ready(None),则在主流程中返回-1

    • 如果存在数据,则调用build_record_batch方法返回Poll::Ready(Some(batch))批数据,在主流程中获取拉取的数据,并返回给JVM中

    • 并设置batch为None,便于进行数据拉取

Centos7 yum 报错「Errno 256」No more mirrors to try 解决方法 首先该问题的出现是由于网络的震荡导致,这是重点。(1)将源更换成阿里云的官方源,步骤参考如上(2)同时将zabbix的官方源更换成阿里云里面的zabbix镜像源,因为zabbix官方源根本网络不通,因为国家防火墙的拦截,导致网络不通。这是阿里源的zabbix镜像地址: https://mirrors.aliyun.com/zabbix/zabbix/4.4/rhel/7/修改系统中早期生成的zabbix.repo文件,如下:主要是把baseurl替换成阿里云的zabbix镜像地址即可。 阅读详情

相关推荐

open-web UI 实用指南

open-web UI 类似于chatgpt的界面,可以私有化模型本地部署,也可以用商业模型.本文只是用开源模型. 它以实现多模态AI聊天 , AI 翻译 , 语音转文本, 文本转语音, 文生图,代码助手,可谓利器。

szmars的博客 3万+

Spark DPP(动态分区裁剪)导致的DataSourceScanExec NullPointerException问题分析以及解决

背景 本文基于spark 3.1.2,且运行在yarn模式下 最近在调试 spark sql的时候遇到了空指针的问题,如下: Caused by: java.lang.NullPointerException at org.apache.spark.sql.execution.DataSourceScanExec.$init$(DataSourceScanExec.scala:57) at org.apache.spark.sql.execution.FileSourceScanExec.<in

monkeyboy_tech的博客 3万+

Yolov26 在 Rk3588 上的 Rknn Onnx 部署实践(c++)

本文介绍了YOLOv26在RK3588平台的C++推理部署方案。重点包括:1)扩展Ultralytics导出格式,新增RKNN_ONNX专用导出选项;2)重构Detect Head结构,使其更适合RKNN推理;3)提供C++侧后处理实现建议。该方案保留了Ultralytics训练生态,通过优化模型结构输出,将解码等操作移至C++端,充分发挥Rockchip NPU性能。适用于RK3588/RK356x等平台,需配合rknn-toolkit2使用,为YOLOv26的嵌入式部署提供完整解决方案。

qq_64366203的博客 2176

SPARK SQL中 CTE(with表达式)会影响性能么?

背景及问题 本文基于spark 3.1.2 最近在排查spark sql问题的时候,出现了一系列的(CTE)with操作,导致该任务运行不出来,而把对应的(CTE)with 替换成了临时表以后,任务很快的就能运行出来 对应的最简化的sql如下: with temp1 as ( select null as user_id ,a.sku_id ,int(sum(`可申请数量`)) `开放申请数量` from xxx.xxx `a` where pt between '20

monkeyboy_tech的博客 7321

双目相机测距原理

普通双目相机具有如下特点:左右两个相机位于同一平面(光轴平行),且相机参数(焦距f)一致。其原理图如下:如图所示,P点为相应的物体位置,CL和CR为左右相机光心位置,PL和PR分别为左相机和右相机拍摄的P点成像结果。f为相机焦距,Z为物体距相机的深度距离,b为左右相机基线距离根据几何关系有:则相应的深度信息计算公式为:(1)其中XL-XR为视差。其余在两侧的情况同样可以用几何关系分析得到该表达式(1),即对于普通双目相机测距可以直接通过公式(1)得到深度结果。

doudou2weiwei的博客 4542

普罗米修斯监控达梦数据库集群

-----------------------------------新增文件---------------------------------------注:图上即为启动成功,如果没有问题,则使用nohup后台启动程序。官网地址:https://github.com/gy297879328/dameng_exporter。注:prometheus的对数据库的指标采集如果高于60s一次,请手动修改提供表盘中的计算值。修改dameng_exporter.config配置文件,修改账号密码的部分。

weixin_49985403的博客 1679

Spark task过多导致任务运行过慢甚至超时

背景以及现象 本文基于 spark 3.1.2 设置spark.driver.memory=2g 在调试spark sql任务的时候,发现有几个任务产生了40多万个Task,而且任务长期运行不出来。 分析 运行此sql,可以得到如下的dag(我们只截取产生Task多的Stage),由此可以看到是scan的文件太大了(scan了日志文件半年的数据)。 但是为什么这种情况下会导致任务运行很缓慢甚至会超时呢? 找到driver端, 用jstat -gcutil查看一下对应的gc情况(对应的内存都是调优完后的镜像

monkeyboy_tech的博客 6799

OpenClaw-遇到的问题-Health check failed: gateway closed (1006 abnormal closure (no close frame)): no clos

OpenClaw网关服务安装成功但运行异常,显示健康检查失败(错误代码1006)。系统提供了Web控制界面(http://127.0.0.1:18789/)和带令牌的安全访问链接。文档建议参考故障排除指南,并提供了可选应用扩展功能(包括移动端支持)。注意运行本地代理存在安全风险,建议加固系统配置。若需启用网络搜索功能,需配置Brave搜索API密钥。安装完成后会自动打开仪表盘,需保持浏览器标签页以控制OpenClaw服务。

直到世界的尽头 1万+

SPARK的计算向量化-已有的向量化项目

背景 在SPARK的计算向量化-spark本身的向量化中,我们提到spark是利用代码生成(生成for循环),从而JIT进行向量化。这是有局限的,所以大部分公司都会采用通过其他方式来进行向量化计算。 我们列举几种已知的应用到spark计算引擎的向量化技术。 Velox Meta(也就是Facebook)开源的一个C++实现的SQL执行引擎,输入逻辑执行计划,利用SIMD指令实现向量化操作,输出结果; 结合Apache Arrow作数据交换,可以实现Spark的计算向量化。 项目见facebookincuba

monkeyboy_tech的博客 4315

YOLOv8改进策略【卷积层】| ECCV-2024 小波卷积WTConv 增大感受野,降低参数量计算量,独家创新助力涨点

本文记录的是利用模块优化的目标检测网络模型。的目的是在不出现过参数化的情况下有效地增加卷积的感受野,从而解决了在感受野扩展中的参数膨胀问题。本文将其加入到深度可分离卷积中,有效降低模型参数量和计算量,并二次创新C2f,使模块更好地捕捉低频特征,增强网络性能。Wavelet Convolutions for Large Receptive Fields小波卷积及其改进的实现代码如下: 四、创新模块 4.1 改进点1⭐ 模块改进方法:加入(第五节讲解添加步骤)。添加后如下:模块改进方法:基于的(第五节讲解添加

Limiiiing的博客 1092

Unity零基础到进阶 ☀️| Unity中的 GC及优化 超级全面解析 ☆(ゝω・)v 建议收藏!

介绍: 在游戏运行的时候,数据主要存储在内存中,当游戏的数据在不需要的时候,存储当前数据的内存就可以被回收以再次使用。内存垃圾是指当前废弃数据所占用的内存,垃圾回收(GC)是指将废弃的内存重新回收再次使用的过程。 Unity中将垃圾回收当作内存管理的一部分,如果游戏中废弃数据占用内存较大,则游戏的性能会受到极大影响,此时垃圾回收会成为游戏性能的一大障碍点。 本文我们主要学习垃圾回收的机制,垃圾回收如何被触发以及如何提GC收效率来提高游戏的性能。 Unity内存管理机制简介 要想了解垃圾回收如何工作以及何时被

努力前行,总会成为自己心中的那道光 6万+

yarn和k8s(kubernetes)混合部署初步方案(利用cgroup资源限制)

版本 yarn 2.6.0 k8s v1.15.2 背景 随着k8s的越来越火热以及k8s的可扩展以及自修复优势,k8s势必是互联网的优势,但是很大一部分公司的资源管理工具依旧是yarn,而且yarn和k8s也必然并行很久一段时间, 但是由于yarn的隔离型相对于k8s来说还是很弱的,好在yarn 2.6.0版本也集成了LinuxContainerExecutor(默认是DefaultContainerExecutor),也就是说yarn在好长一段时间是不支持cpu限制的,LinuxContainerExe

monkeyboy_tech的博客 4158

游戏大厂如何解析引擎技术

本文系统解析了游戏大厂如何评估和定制游戏引擎。游戏引擎作为游戏开发的核心基础,大厂会从性能、跨平台支持、渲染能力、网络模块等8个维度进行技术解析,通过文档查勘、源码阅读、Demo测试等7个步骤深度评估。解析后通常有三种决策:直接使用、深度定制或自研重构。文章以腾讯、网易、米哈游等为例,展示了不同厂商的引擎定制策略,并分析了技术解析面临的挑战和未来趋势。最终指出,体系化的引擎技术解析是打造高质量游戏的关键,需要持续的技术投入和团队协作。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 535

SPARK Expand问题的解决(由count distinct、group sets、cube、rollup引起的)

背景 本文基于spark 3.1.2 我们知道spark对于count(distinct)/group sets 以及cube、rollup的处理都是采用转换为Expand的方法处理, 这样做的优点就是在数据量小的情况下,能有以空间换时间,从而达到加速的目的。 但是弊端也是很明显,就是在数据量较大的情况下,而且expand的倍数达到上百倍或者千倍的时候,这任务运行的时间很长(这在数分中是非常常见的)。 分析 先来看一组图: 是不是很刺激,数据从2,635,978,109直接扩张到了168,702,598,

monkeyboy_tech的博客 4010

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

SPARK 是怎么清除Shuffle中间结果数据的

背景 本文基于 SPARK 3.2.1 我们知道在Spark中,shuffle的数据会存在ESS或者BlockManager中的,但是spark什么时候会删除shuffle的中间结果数据呢?这篇文章我们来分析一下 分析 我们知道spark肯定会删除shuffle结果数据,要不然长期以往,磁盘肯定会爆掉。 其中清除的组件就是ContextCleaner,代码如下: private[spark] class ContextCleaner( sc: SparkContext, shuffleDri

monkeyboy_tech的博客 3839

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Spark做TPC-DS性能测试

最近由于在做上云的工作,并且公司离线部分引擎是Spark,所以做了一次基于TPC-DS性能比对测试。生产了大约200GB的数据。

monkeyboy_tech的博客 3701

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Spark中的WholeStageCodegenExec(全代码生成)

背景 在之前的文章中Spark DPP(动态分区裁剪)导致的DataSourceScanExec NullPointerException问题分析以及解决,我们直接跳过了动态代码生成失败这版本一步部分,这次我们来分析一下,SQL还是在以上提到的文章中。 分析 运行完该sql,我们可以看到如下的物理计划: 我们看到FilterExec和ColumnarRoRowExec并没有在一个WholeStageCodegen 中, 这是为什么呢? 这是因为exists方法是继承自CodegenFallback Tr

monkeyboy_tech的博客 3466

Spark CTAS union all (union all的个数很多)导致超过spark.driver.maxResultSize配置(1G)

spark CTAS nuion all (union all的个数很多)导致超过spark.driver.maxResultSize配置(2G)

monkeyboy_tech的博客 3041

深入理解SPARK SQL 中HashAggregateExec和ObjectHashAggregateExec以及UnsafeRow

感悟和理解 翻译这篇文章是为了更好的理解ObjectHashAggregateExec和UnsafeRow,关于UnsafeRow的文章,可以参考Spark源码修改系列 - UnsafeRow内存布局和代码优化。 其实明显,UnsafeRow是模拟了offheap的分配方式而在堆上进行的操作,它并没有脱离了jvm,而是使用了Java unsafe API(Java提供的底层非安全操作接口),允许用户像操作C++指针一样来申请和访问内存地址,JVM也不会对这部分非安全数据进行额外的类型检查和垃圾回收。 事实上

monkeyboy_tech的博客 2960

Spark shuffle(ExchangeExec)过多导致任务运行过慢甚至超时

背景以及现象 本文基于 spark 3.1.2 设置spark.driver.memory=2g 在调试spark sql任务的时候,发现有任务产生了200多个exchange,而且任务长期运行不出来。 分析 运行对应的sql(多个连续的join操作,且join的key都不一样),得到如下的物理计划(我们只截取了一部分): 和之前的文章spark task过多导致任务运行过慢甚至超时 做法一样(对应的内存都是调优完后的镜像信息),三部曲如下: 用jstat -gcutil查看一下对应的gc情况,如下:

monkeyboy_tech的博客 2913

Spark 中的 Rebalance 操作以及与Repartition操作的区别

本文基本spark 3.2.1 在Partitioning Hints Types中有提到Rebalance操作以及Repartition操作,而且他们都可以做数据的重分区,他们之间有什么区别呢? 分析 Rebalance 参考对应的SPARK-35725,其目的是为了在AQE阶段,根据进行分区的重新分区,防止数据倾斜。再加上SPARK-35786,就可以根据hint进行重分区。 具体看看怎么实现的,OptimizeSkewInRebalancePartitions代码如下: 只有开启了 了的情况下,

monkeyboy_tech的博客 2880

SPARK中 会对Scan的小文件做合并到一个Task去处理么?

背景 本文基于SPARK 3.1.2 在之前查看SQL物理计划的时候,发现一个很奇怪的现象,文件的个数很多,但是启动的Task却很少。 结论 SPARK在scan文件的时候,会把小文件合并到一个Task上去处理。 分析 这里的SQL很简单:就是select col from table语句我们直接查看对应的计划: 可以看到对于有50000多个文件的source,最终却只有6000多个任务运行。 我们直接看对应的代码FileSourceScanExec实现: val splitFiles = se

monkeyboy_tech的博客 2785

Spark在生产中是否要禁止掉BHJ(BroadcastHashJoin)

背景 本文基于spark 3.2 driver内存 2G 问题描述 在基于复杂的sql运行中,或者说是存在多个join操作的sql中,如果说driver内存不是很大的情况下,我们经常会遇到如下报错: Caused by: org.apache.spark.SparkException: Could not execute broadcast in 800 secs. You can increase the timeout for broadcasts via spark.sql.broadcastTime

monkeyboy_tech的博客 2682

【计算机毕业设计】基于Hadoop的公共交通运营数据分析与可视化系统 大数据毕业设计选题 Hadoop Spark Numpy Pandas Echarts

基于Hadoop生态的公共交通运营数据分析与可视化系统,利用Spark处理海量公交数据,结合K-Means聚类、Pandas/Numpy分析,实现线路、站点、区段等多维度统计。通过Django提供接口,Vue+ElementUI搭建前端,Echarts实现城市规模、线路特征、站点网络等动态可视化看板,构建从数据采集到可视化的完整闭环,助力智慧交通决策。支持机器学习模型定制,文末可获取源码。

weixin_19164791635 749

最新大数据毕业设计选题推荐-基于大数据的电影评分与热度数据可视化分析-大数据-Spark-Hadoop-Bigdata

本文介绍《基于大数据的电影评分与热度数据可视化分析系统》,采用Hadoop+Spark构建大数据处理链路,结合Django/Spring Boot后端与Vue+ECharts前端,实现电影评分、热度数据的采集、清洗、分析与可视化。系统支持高分影片管理、评分分布、热度传播趋势等多维度展示,提供可运行、可扩展的毕业设计参考,涵盖完整技术栈与源码,助力学生掌握大数据项目全流程开发。

IT研究室的博客 253

【Spark】 04键值对 RDD:创建、常用操作与数据分区

介绍 Spark 键值对 RDD 的创建方式、聚合、分组、连接、排序与行动操作,并结合 PageRank 示例讲解数据分区、分区器复用与自定义分区方式。

qq_46126258的博客 363
上一篇: oh-my-opencode 实践--用户授权登陆Spring Boot项目
下一篇: Spark Datafusion Comet 向量化Rust Native--Native算子指标如何传递到Spark UI上展示
鸿乃江边鸟
博客等级 码龄6年 3746粉丝 235原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值