从JDBC源迁移数据时如何优化分区?

时间:2018-10-02 06:38:24

标签: apache-spark jdbc hive apache-spark-sql partitioning

我试图将数据从PostgreSQL表中的表移到HDFS上的Hive表中。为此,我想出了以下代码:

  val conf  = new SparkConf().setAppName("Spark-JDBC").set("spark.executor.heartbeatInterval","120s").set("spark.network.timeout","12000s").set("spark.sql.inMemoryColumnarStorage.compressed", "true").set("spark.sql.orc.filterPushdown","true").set("spark.serializer", "org.apache.spark.serializer.KryoSerializer").set("spark.kryoserializer.buffer.max","512m").set("spark.serializer", classOf[org.apache.spark.serializer.KryoSerializer].getName).set("spark.streaming.stopGracefullyOnShutdown","true").set("spark.yarn.driver.memoryOverhead","7168").set("spark.yarn.executor.memoryOverhead","7168").set("spark.sql.shuffle.partitions", "61").set("spark.default.parallelism", "60").set("spark.memory.storageFraction","0.5").set("spark.memory.fraction","0.6").set("spark.memory.offHeap.enabled","true").set("spark.memory.offHeap.size","16g").set("spark.dynamicAllocation.enabled", "false").set("spark.dynamicAllocation.enabled","true").set("spark.shuffle.service.enabled","true")
  val spark = SparkSession.builder().config(conf).master("yarn").enableHiveSupport().config("hive.exec.dynamic.partition", "true").config("hive.exec.dynamic.partition.mode", "nonstrict").getOrCreate()
  def prepareFinalDF(splitColumns:List[String], textList: ListBuffer[String], allColumns:String, dataMapper:Map[String, String], partition_columns:Array[String], spark:SparkSession): DataFrame = {
        val colList                = allColumns.split(",").toList
        val (partCols, npartCols)  = colList.partition(p => partition_columns.contains(p.takeWhile(x => x != ' ')))
        val queryCols              = npartCols.mkString(",") + ", 0 as " + flagCol + "," + partCols.reverse.mkString(",")
        val execQuery              = s"select ${allColumns}, 0 as ${flagCol} from schema.tablename where period_year='2017' and period_num='12'"
        val yearDF                 = spark.read.format("jdbc").option("url", connectionUrl).option("dbtable", s"(${execQuery}) as year2017")
                                                                      .option("user", devUserName).option("password", devPassword)
                                                                      .option("partitionColumn","cast_id")
                                                                      .option("lowerBound", 1).option("upperBound", 100000)
                                                                      .option("numPartitions",70).load()
        val totalCols:List[String] = splitColumns ++ textList
        val cdt                    = new ChangeDataTypes(totalCols, dataMapper)
        hiveDataTypes              = cdt.gpDetails()
        val fc                     = prepareHiveTableSchema(hiveDataTypes, partition_columns)
        val allColsOrdered         = yearDF.columns.diff(partition_columns) ++ partition_columns
        val allCols                = allColsOrdered.map(colname => org.apache.spark.sql.functions.col(colname))
        val resultDF               = yearDF.select(allCols:_*)
        val stringColumns          = resultDF.schema.fields.filter(x => x.dataType == StringType).map(s => s.name)
        val finalDF                = stringColumns.foldLeft(resultDF) {
          (tempDF, colName) => tempDF.withColumn(colName, regexp_replace(regexp_replace(col(colName), "[\r\n]+", " "), "[\t]+"," "))
        }
        finalDF
  }
    val dataDF = prepareFinalDF(splitColumns, textList, allColumns, dataMapper, partition_columns, spark)
    val dataDFPart = dataDF.repartition(30)
    dataDFPart.createOrReplaceTempView("preparedDF")
    spark.sql("set hive.exec.dynamic.partition.mode=nonstrict")
    spark.sql("set hive.exec.dynamic.partition=true")
    spark.sql(s"INSERT OVERWRITE TABLE schema.hivetable PARTITION(${prtn_String_columns}) select * from preparedDF")

将数据插入基于prtn_String_columns: source_system_name, period_year, period_num动态分区的配置单元表中

已使用火花提交:

SPARK_MAJOR_VERSION=2 spark-submit --conf spark.ui.port=4090 --driver-class-path /home/fdlhdpetl/jars/postgresql-42.1.4.jar  --jars /home/fdlhdpetl/jars/postgresql-42.1.4.jar --num-executors 80 --executor-cores 5 --executor-memory 50G --driver-memory 20G --driver-cores 3 --class com.partition.source.YearPartition splinter_2.11-0.1.jar --master=yarn --deploy-mode=cluster --keytab /home/fdlhdpetl/fdlhdpetl.keytab --principal fdlhdpetl@FDLDEV.COM --files /usr/hdp/current/spark2-client/conf/hive-site.xml,testconnection.properties --name Splinter --conf spark.executor.extraClassPath=/home/fdlhdpetl/jars/postgresql-42.1.4.jar

在执行程序日志中生成以下错误消息:

Container exited with a non-zero exit code 143.
Killed by external signal
18/10/03 15:37:24 ERROR SparkUncaughtExceptionHandler: Uncaught exception in thread Thread[SIGTERM handler,9,system]
java.lang.OutOfMemoryError: Java heap space
    at java.util.zip.InflaterInputStream.<init>(InflaterInputStream.java:88)
    at java.util.zip.ZipFile$ZipFileInflaterInputStream.<init>(ZipFile.java:393)
    at java.util.zip.ZipFile.getInputStream(ZipFile.java:374)
    at java.util.jar.JarFile.getManifestFromReference(JarFile.java:199)
    at java.util.jar.JarFile.getManifest(JarFile.java:180)
    at sun.misc.URLClassPath$JarLoader$2.getManifest(URLClassPath.java:944)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:450)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at org.apache.spark.util.SignalUtils$ActionHandler.handle(SignalUtils.scala:99)
    at sun.misc.Signal$1.run(Signal.java:212)
    at java.lang.Thread.run(Thread.java:745)

我在日志中看到,使用给定数量的分区,读取已正确执行,如下所示:

Scan JDBCRelation((select column_names from schema.tablename where period_year='2017' and period_num='12') as year2017) [numPartitions=50]

以下是分阶段执行者的状态: enter image description here

enter image description here

enter image description here

enter image description here

数据未正确分区。一个分区较小,而另一个分区较大。这里有一个偏斜问题。 将数据插入Hive表时,作业在以下行失败:spark.sql(s"INSERT OVERWRITE TABLE schema.hivetable PARTITION(${prtn_String_columns}) select * from preparedDF"),但我知道这是由于数据偏斜问题而发生的。

我试图增加执行程序的数量,增加执行程序的内存,驱动程序的内存,试图将其另存为csv文件,而不是将数据帧保存到Hive表中,但不会因给出异常而影响执行:

java.lang.OutOfMemoryError: GC overhead limit exceeded

代码中是否需要纠正?谁能让我知道如何解决这个问题?

3 个答案:

答案 0 :(得分:4)

  1. 根据输入数据量和群集资源确定所需的分区数。根据经验,除非绝对必要,否则最好将分区输入保持在1GB以下。并且严格小于块大小限制。

    您已经previously stated迁移了用于不同帖子(5-70)中的1TB数据值,这很可能会降低到确保平稳处理的程度。

    尝试使用不需要进一步repartitioning的价值。

  2. 了解您的数据。

    分析数据集中的可用列,以确定是否有任何具有高基数和均匀分布的列要分布在所需数量的分区中。这些是导入过程的良好候选者。另外,您应该确定一个确切的值范围。

    具有不同中心度和偏度度量的聚合以及直方图和按键的基本计数是很好的探索工具。对于这一部分,最好直接在数据库中分析数据,而不是将其提取到Spark。

    根据RDBMS的不同,您可能可以使用width_bucket(PostgreSQL,Oracle)或同等功能来了解如何在装载partitionColumn,{{1 }},lowerBoundupperBound

    numPartitons
  3. 如果没有满足上述条件的列,请考虑:

    • 创建一个自定义对象并通过暴露它。一个看法。多个独立列上的哈希通常是不错的选择。请查阅数据库手册,以确定可以在此处使用的功能(在Oracle中为s"""(SELECT width_bucket($partitionColum, $lowerBound, $upperBound, $numPartitons) AS bucket, COUNT(*) FROM t GROUP BY bucket) as tmp)""" ,在PostgreSQL中为DBMS_CRYPTO)。
    • 使用一组独立的列,这些列在一起提供了足够高的基数。

      (可选)如果要写入分区的Hive表,则应考虑包括Hive分区列。这可能会限制以后生成的文件数量。

  4. 准备分区参数

    • 如果在上一步中选择或创建的列是数字(or date / timestamp in Spark >= 2.4),则直接将其作为pgcrypto提供,并使用之前确定的范围值来填充partitionColumn和{{ 1}}。

      如果绑定值不能反映数据的属性(lowerBound的{​​{1}},upperBound的{​​{1}})可能会导致大量数据偏斜,因此请谨慎处理线程。在最坏的情况下,当边界不覆盖数据范围时,所有记录将由一台计算机获取,这使其根本不比完全没有分区好。

    • 如果在前面的步骤中选择的列是类别列或一组列,则将生成可以完全覆盖数据的互斥谓词列表,其形式可以用于min(col) where子句。

      例如,如果您的列lowerBound的值为{max(col)upperBoundSQL},列A的值为{{{1} },a1a2}:

      a3

      仔细检查条件是否重叠,是否涵盖所有组合。如果不满足这些条件,则最终将导致重复或缺少记录。

      将数据作为B参数传递给b1调用。请注意,分区的数量将与谓词的数量完全相同。

  5. 以只读模式放置数据库(任何正在进行的写操作都可能导致数据不一致。如果可能,应在开始整个过程​​之前锁定数据库,但如果不能,则在组织中)。

  6. 如果没有b2的分区数与所需的输出负载数据匹配并直接转储到接收器,则如果不符合,则可以尝试按照与步骤1相同的规则进行重新分区。

  7. 如果仍然遇到任何问题,请确保已正确配置了Spark内存和GC选项。

  8. 如果以上方法均无效:

    • 考虑将数据转储到网络上/使用b3之类的工具分配存储并直接从那里读取。

      请注意,使用标准的数据库实用程序通常需要POSIX兼容的文件系统,因此HDFS通常不需要。

      此方法的优点是您不必担心列属性,也无需将数据置于只读模式即可确保一致性。

    • 使用专用的批量传输工具(例如Apache Sqoop),然后再重塑数据。


* 不要使用伪列-Pseudocolumn in Spark JDBC

答案 1 :(得分:1)

根据我的经验,有四种内存设置会有所不同:

A)[1]用于处理数据的存储器VS [2]用于保存程序堆栈的堆空间

B)[1]驱动程序VS [2]执行程序内存

到目前为止,通过增加适当的内存种类,我始终能够使我的Spark作业成功运行:

因此,

A2-B1将在驱动程序上有可用的内存来保存程序堆栈。等等

属性名称如下:

A1-B1)executor-memory

A1-B2)driver-memory

A2-B1) spark.yarn.executor.memoryOverhead

A2-B2) spark.yarn.driver.memoryOverhead

请记住,所有* -B1的总和必须小于工作线程上的可用内存,所有* -B2的总和必须小于驱动程序节点上的内存。

我敢打赌,罪魁祸首是加粗标记的堆设置之一。

答案 2 :(得分:0)

您的另一个问题是重复

 'How to avoid data skewing while reading huge datasets or tables into spark? 
  The data is not being partitioned properly. One partition is smaller while the 
  other one becomes huge on read.
  I observed that one of the partition has nearly 2million rows and 
  while inserting there is a skew in partition. '

如果问题在于处理读取后在数据帧中分区的数据,您是否在尝试增加“ numPartitions”值?

.option("numPartitions",50)

lowerBound, upperBound的表单分区步幅用于生成的WHERE子句表达式,而numpartitions确定拆分的数量。

例如,某张表具有列ID(我们将其选择为partitionColumn);我们在表格中看到的列ID的值范围是1到1000,我们想通过运行select * from sometable来获取所有记录, 所以我们使用lowerbound = 1&upperbound = 1000和numpartition = 4

这将通过根据我们的供稿(lowerbound = 1 & upperbound = 1000 and numpartition = 4)构建sql来生成一个包含4个分区的数据帧,其中每个查询的结果

select * from sometable where ID < 250
select * from sometable where ID >= 250 and ID < 500
select * from sometable where ID >= 500 and ID < 750
select * from sometable where ID >= 750

如果我们表中的大多数记录属于ID(500,750)范围之内,该怎么办。这就是你要面对的情况。

当我们增加numpartition时,拆分会更进一步,这会减少同一分区中的记录量,但这 不是一个好镜头。

我们不是根据我们提供的边界来对partitioncolumn进行分割,而是考虑到自己提供分割,因此数据可以是均匀的 分裂了。您需要切换到另一种JDBC方法,我们可以在其中代替(lowerbound,upperbound & numpartition) 谓词直接。

def jdbc(url: String, table: String, predicates: Array[String], connectionProperties: Properties): DataFrame 

Link