内存有限的情况下 Spark 如何处理 T 级别的数据?

简单起见,下述答案仅就无shuffle的单stage Spark作业做了概要解释。对于多stage任务而言,在内存的使用上还有很多其他重要问题没有覆盖。部分内容请参考评论中  @邵赛赛  给出的补充。Spark确实擅长内存计算,内存容量不足时也可以回退,但题主给出的条件(8GB内存跑1TB数据)也确实是过于苛刻了……

首先需要解开的一个误区是,对于Spark这类内存计算系统,并不是说要处理多大规模的数据就需要多大规模的内存。Spark相对Hadoop MR有大幅性能提升的一个前提就是大量大数据作业同一时刻需要加载进内存的数据只是整体数据的一个子集,且大部分情况下可以完全放入内存,正如Shark(Spark上的Hive兼容的data warehouse)论文1.1节所述:

In fact, one study [1] analyzed the access patterns in the Hive warehouses at Facebook and discovered that for the vast majority (96%) of jobs, the entire inputs could fit into a fraction of the cluster’s total memory.


[1] G. Ananthanarayanan, A. Ghodsi, S. Shenker, and I. Stoica. Disk-locality in datacenter computing considered irrelevant. In HotOS ’11, 2011.

至于数据子集仍然无法放入集群物理内存的情况,Spark仍然可以妥善处理,下文还会详述。

在Spark内部,单个executor进程内RDD的分片数据是用Iterator流式访问的,Iterator的hasNext方法和next方法是由RDD lineage上各个transformation携带的闭包函数复合而成的。该复合Iterator每访问一个元素,就对该元素应用相应的复合函数,得到的结果再流式地落地(对于shuffle stage是落地到本地文件系统留待后续stage访问,对于result stage是落地到HDFS或送回driver端等等,视选用的action而定)。如果用户没有要求Spark cache该RDD的结果,那么这个过程占用的内存是很小的,一个元素处理完毕后就落地或扔掉了(概念上如此,实现上有buffer),并不会长久地占用内存。只有在用户要求Spark cache该RDD,且storage level要求在内存中cache时,Iterator计算出的结果才会被保留,通过cache manager放入内存池。

简单起见,暂不考虑带shuffle的多stage情况和流水线优化。这里拿最经典的log处理的例子来具体说明一下(取出所有以ERROR开头的日志行,按空格分隔并取第2列):
val lines = spark.textFile("hdfs://<input>")
val errors = lines.filter(_.startsWith("ERROR"))
val messages = errors.map(_.split(" ")(1))
messages.saveAsTextFile("hdfs://<output>")
按传统单机immutable FP的观点来看,上述代码运行起来好像是:
  1. 把HDFS上的日志文件全部拉入内存形成一个巨大的字符串数组,
  2. Filter一遍再生成一个略小的新的字符串数组,
  3. 再map一遍又生成另一个字符串数组。
真这么玩儿的话Spark早就不用混了……

如前所述,Spark在运行时动态构造了一个复合Iterator。就上述示例来说,构造出来的Iterator的逻辑概念上大致长这样:
new Iterator[String] {
  private var head: String = _
  private var headDefined: Boolean = false

  def hasNext: Boolean = headDefined || {
    do {
      try head = readOneLineFromHDFS(...)     // (1) read from HDFS
      catch {
        case _: EOFException => return false
      }
    } while (!head.startsWith("ERROR"))       // (2) filter closure
    true
  }

  def next: String = if (hasNext) {
    headDefined = false
    head.split(" ")(1)                        // (3) map closure
  } else {
    throw new NoSuchElementException("...")
  }
}
上面这段代码是我按照Spark中FilteredRDD、MappedRDD的定义和Scala Iterator的filter、map方法的框架写的伪码,并且省略了从cache或checkpoint中读取现成结果的逻辑。1、2、3三处便是RDD lineage DAG中相应逻辑嵌入复合出的Iterator的大致方式。每种RDD变换嵌入复合Iterator的具体方式是由不同的RDD以及Scala Iterator的相关方法定义的。可以看到,用这个Iterator访问整个数据集,空间复杂度是O(1)。可见,Spark RDD的immutable语义并不会造成大数据内存计算任务的庞大内存开销。

然后来看加cache的情况。我们假设errors这个RDD比较有用,除了拿出空格分隔的第二列以外,可能在同一个application中我们还会再频繁用它干别的事情,于是选择将它cache住:
val lines = spark.textFile("hdfs://<input>")
val errors = lines.filter(_.startsWith("ERROR")).cache()  // <-- !!!
val messages = errors.map(_.split(" ")(1))
messages.saveAsTextFile("hdfs://<output>")
加了cache之后有什么变化呢?实际上相当于在上述复合Iterator伪码的(2)处,将filter出来的文本行逐一追加到了内存中的一个ArrayBuffer[String]里存起来形成一个block,然后通过cache manager扔进受block manager管理的内存池。注意这里仅仅cache了filter出来的结果,HDFS读出的原始数据没有被cache,对errors做map操作后得到的messages RDD也没有被cache。这样一来,后续任务复用errors这个RDD时,直接从内存中取就好,就不用重新计算了。
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java Spark可以通过以下几种方式读取Excel数据: 1. 使用Apache POI库读取Excel数据,然后将数据转换为Spark DataFrame。 2. 使用Excel DataSource API,这是一个基于Spark SQL的插件,可以直接从Excel中加载数据并生成Spark DataFrame。 3. 使用第三方库,例如DataBricks的excelent或者CData的Excel JDBC驱动程序。 下面是使用Apache POI库读取Excel数据的示例代码: ```java import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.apache.spark.sql.types.DataTypes; import org.apache.spark.sql.types.StructField; import org.apache.spark.sql.types.StructType; import org.apache.spark.sql.functions.*; import java.util.ArrayList; import java.util.List; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; public class ExcelReader { public static void main(String[] args) { SparkSession spark = SparkSession.builder() .appName("Excel Reader") .master("local[*]") .getOrCreate(); String filePath = "path/to/excel/file.xlsx"; String sheetName = "Sheet1"; // Read Excel file into a Workbook object Workbook workbook = null; try { workbook = new XSSFWorkbook(filePath); } catch (Exception e) { e.printStackTrace(); } // Get the sheet from the workbook Sheet sheet = workbook.getSheet(sheetName); // Get the header row Row headerRow = sheet.getRow(0); // Create a list of StructField objects to define the schema List<StructField> fields = new ArrayList<>(); for (Cell cell : headerRow) { String columnName = cell.getStringCellValue(); StructField field = DataTypes.createStructField(columnName, DataTypes.StringType, true); fields.add(field); } // Create the schema StructType schema = DataTypes.createStructType(fields); // Read the data rows and convert them to Spark Rows List<Row> rows = new ArrayList<>(); for (int i = 1; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); List<String> rowValues = new ArrayList<>(); for (Cell cell : row) { rowValues.add(cell.getStringCellValue()); } Row sparkRow = RowFactory.create(rowValues.toArray()); rows.add(sparkRow); } // Create the DataFrame Dataset<Row> df = spark.createDataFrame(rows, schema); // Show the DataFrame df.show(); // Close the workbook try { workbook.close(); } catch (Exception e) { e.printStackTrace(); } } } ``` 注意:这段代码仅适用于读取XLSX格式的Excel文件,如果要读取XLS格式的文件,需要使用HSSF而不是XSSF。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值