数量重新分配逻辑 - 具有外部数据集的 MapGroups

2024-05-19

我正在研究一种复杂的逻辑,需要将数量从一个数据集重新分配到另一个数据集。

在例子中我们有Owner and Invoice- 我们需要从数量中减去Invoice准确地Owner匹配(在给定汽车的给定邮政编码处)。 减去的数量需要重新分配回同一辆车出现的其他邮政编码。 复杂性发生在我们应该避免分配到邮政编码的地方,因为同一辆车的发票表中存在另一个 pcode。

最后,如果减法或重新分布产生负值,我们应该避免对给定的这种变换Invoice.

这是一个带有数字的示例

下面是代码版本,但不幸的是它没有按预期工作。更具体地说,我不知道如何跳过给定汽车的发票中多次出现的记录。 在第一个示例(红色)中,我不知道如何跳过记录 Owner(A, 888, 100)。

package playground

import org.apache.spark.sql.SparkSession


object basic extends App {
  val spark = SparkSession
    .builder()
    .appName("Sample app")
    .master("local")
    .getOrCreate()

  import spark.implicits._

  final case class Owner(car: String, pcode: String, qtty: Double)
  final case class Invoice(car: String, pcode: String, qtty: Double)

  val sc = spark.sparkContext

  val data = Seq(
    Owner("A", "666", 80),
    Owner("B", "555", 20),
    Owner("A", "444", 50),
    Owner("A", "222", 20),
    Owner("C", "444", 20),
    Owner("C", "666", 80),
    Owner("C", "555", 120),
    Owner("A", "888", 100)
  )

  val fleet = Seq(
    Invoice("A", "666", 15),
    Invoice("C", "444", 10),
    Invoice("A", "888", 12),
    Invoice("B", "555", 200)
  )

  val owners = spark.createDataset(data)
  val invoices = spark.createDataset(fleet)

  val actual = owners
    .joinWith(invoices, owners("Car") === invoices("Car"), joinType = "right")
    .groupByKey(_._2)
    .flatMapGroups {
      case (invoice, group) =>
        val subOwner: Vector[Owner] = group.toVector.map(_._1)
        val householdToBeInvoiced: Vector[Owner] =
          subOwner.filter(_.pcode == invoice.pcode)
        val modifiedOwner: Vector[Owner] = if (householdToBeInvoiced.nonEmpty) {
          // negative compensation (remove the quantity from Invoice for the exact match)
          val neg: Owner = householdToBeInvoiced.head
          val calculatedNeg: Owner = neg.copy(qtty = neg.qtty - invoice.qtty)

          // positive compensation (redistribute the "removed" quantity proportionally but not for pcode existing in
          // invoice for the same car
          val pos = subOwner.filter(s => s.pcode != invoice.pcode)
          val totalQuantityOwner = pos.map(_.qtty).sum
          val calculatedPos: Vector[Owner] =
            pos.map(
              c =>
                c.copy(
                  qtty = c.qtty + invoice.qtty * c.qtty / (totalQuantityOwner - neg.qtty)
              )
            )

          (calculatedPos :+ calculatedNeg)
        } else {
          subOwner
        }

        modifiedOwner
    }
}

这段代码产生

+---+-----+------------------+
|car|pcode|              qtty|
+---+-----+------------------+
|  A|  888|116.66666666666667|
|  A|  222|23.333333333333332|
|  A|  444|58.333333333333336|
|  A|  666|              65.0|
|  C|  555|126.66666666666667|
|  C|  666| 84.44444444444444|
|  C|  444|              10.0|
|  B|  555|            -180.0|
|  A|  222|              24.8|
|  A|  444|              62.0|
|  A|  666|              99.2|
|  A|  888|              88.0|
+---+-----+------------------+

任何支持将不胜感激!谢谢


经过对这个问题的更多思考,我设法改进了代码,但我仍然无法获得迭代方法(使用前一个计算来计算下一个,例如获取红色记录的结果来生成蓝色记录等。 )

package playground

import org.apache.log4j.{Level, Logger}
import org.apache.spark.sql.{Dataset, KeyValueGroupedDataset, SparkSession}

object basic extends App {

  Logger.getLogger("org").setLevel(Level.OFF)
  Logger.getLogger("akka").setLevel(Level.OFF)

  val spark = SparkSession
    .builder()
    .appName("Spark Optimization Playground")
    .master("local")
    .getOrCreate()

  import spark.implicits._

  final case class Owner(car: String, pcode: String, qtty: Double)
  final case class Invoice(car: String, pcode: String, qtty: Double)

  val data = Seq(
    Owner("A", "666", 80),
    Owner("B", "555", 20),
    Owner("A", "444", 50),
    Owner("A", "222", 20),
    Owner("C", "444", 20),
    Owner("C", "666", 80),
    Owner("C", "555", 120),
    Owner("A", "888", 100)
  )

  val fleet = Seq(
    Invoice("A", "666", 15),
    Invoice("C", "444", 10),
    Invoice("A", "888", 12),
    Invoice("B", "555", 200)
  )

  val owners = spark.createDataset(data)
  val invoices = spark.createDataset(fleet)

  val secondFleets = invoices.map(identity)

  val fleetPerCar =
    invoices
      .joinWith(secondFleets, invoices("car") === secondFleets("car"), "inner")
      .groupByKey(_._1)
      .flatMapGroups {
        case (value, iter) ⇒ Iterator((value, iter.toArray))
      }

  val gb
    : KeyValueGroupedDataset[(Invoice, Array[(Invoice, Invoice)]),
                             (Owner, (Invoice, Array[(Invoice, Invoice)]))] =
    owners
      .joinWith(fleetPerCar, owners("car") === fleetPerCar("_1.car"), "right")
      .groupByKey(_._2)

  val x: Dataset[Owner] =
    gb.flatMapGroups {
      case (fleet, group) =>
        val subOwner: Vector[Owner] = group.toVector.map(_._1)
        val householdToBeInvoiced: Vector[Owner] =
          subOwner.filter(_.pcode == fleet._1.pcode)
        val modifiedOwner: Vector[Owner] = if (householdToBeInvoiced.nonEmpty) {
          // negative compensation (remove the quantity from Invoice for the exact match)
          val neg: Owner = householdToBeInvoiced.head
          val calculatedNeg: Owner = neg.copy(qtty = neg.qtty - fleet._1.qtty)

          // positive compensation (redistribute the "removed" quantity proportionally but not for pcode existing in
          // invoice for the same car
          val otherPCode =
            fleet._2.filter(_._2.pcode != fleet._1.pcode).map(_._2.pcode)

          val pos = subOwner.filter(
            s => s.pcode != fleet._1.pcode && !otherPCode.contains(s.pcode)
          )
          val totalQuantityOwner = pos.map(_.qtty).sum + neg.qtty
          val calculatedPos: Vector[Owner] =
            pos.map(
              c =>
                c.copy(
                  qtty = c.qtty + fleet._1.qtty * c.qtty / (totalQuantityOwner - neg.qtty)
              )
            )
          // if pos or neg compensation produce negative quantity, skip the computation
          val res = (calculatedPos :+ calculatedNeg)
          if (res.exists(_.qtty < 0)) {
            subOwner
          } else {
            res
          }
        } else {
          subOwner
        }

        modifiedOwner
    }
  x.show()
}

第一个解决方案基于Spark Datasets and SparkSQL并提供预期结果。

有很多方法可以配置这种方法,甚至考虑到性能问题,这可能会在稍后讨论。

import org.apache.spark.sql.SparkSession
import org.apache.log4j.{Level, Logger}

object basic {

  val spark = SparkSession
    .builder()
    .appName("Sample app")
    .master("local")
    .config("spark.sql.shuffle.partitions","200") //Change to a more reasonable default number of partitions for our data
    .getOrCreate()

  val sc = spark.sparkContext

  case class Owner(car: String, pcode: String, qtty: Double)
  case class Invoice(car: String, pcode: String, qtty: Double)

  def main(args: Array[String]): Unit = {

    val data = Seq(
      Owner("A", "666", 80),
      Owner("B", "555", 20),
      Owner("A", "444", 50),
      Owner("A", "222", 20),
      Owner("C", "444", 20),
      Owner("C", "666", 80),
      Owner("C", "555", 120),
      Owner("A", "888", 100)
    )

    val fleet = Seq(
      Invoice("A", "666", 15),
      Invoice("C", "666", 10),
      Invoice("A", "888", 12),
      Invoice("B", "555", 200)
    )

    val expected = Seq(
      Owner("A", "666", 65),
      Owner("B", "555", 20), // not redistributed because produce a negative value
      Owner("A", "444", 69.29),
      Owner("A", "222", 27.71),
      Owner("C", "444", 21.43),
      Owner("C", "666", 70),
      Owner("C", "555", 128.57),
      Owner("A", "888", 88)
    )

    Logger.getRootLogger.setLevel(Level.ERROR)

    try {
      import spark.implicits._

      val owners = spark.createDataset(data).as[Owner].cache()
      val invoices = spark.createDataset(fleet).as[Invoice].cache()

      owners.createOrReplaceTempView("owners")
      invoices.createOrReplaceTempView("invoices")

      /**
        * this part fetch car and pcode from owner with the substracted quantity from invoice
        */
      val p1 = spark.sql(
        """SELECT i.car,i.pcode,
          |CASE WHEN (o.qtty - i.qtty) < 0 THEN o.qtty ELSE (o.qtty - i.qtty) END AS qtty,
          |CASE WHEN (o.qtty - i.qtty) < 0 THEN 0 ELSE i.qtty END AS to_distribute
          |FROM owners o
          |INNER JOIN invoices i  ON(i.car = o.car AND i.pcode = o.pcode)
          |""".stripMargin)
        .cache()
      p1.createOrReplaceTempView("p1")

      /**
        * this part fetch all the car and pcode that we have to redistribute their quantity
        */
      val p2 = spark.sql(
        """SELECT o.car, o.pcode, o.qtty
          |FROM owners o
          |LEFT OUTER JOIN invoices i  ON(i.car = o.car AND i.pcode = o.pcode)
          |WHERE i.car IS NULL
          |""".stripMargin)
        .cache()
      p2.createOrReplaceTempView("p2")

      /**
        * this part fetch the quantity to distribute
        */
      val distribute = spark.sql(
        """
          |SELECT car, SUM(to_distribute) AS to_distribute
          |FROM p1
          |GROUP BY car
          |""".stripMargin)
        .cache()
      distribute.createOrReplaceTempView("distribute")

      /**
        * this part fetch the proportion to distribute proportionally
        */
      val proportion = spark.sql(
        """
          |SELECT car, SUM(qtty) AS proportion
          |FROM p2
          |GROUP BY car
          |""".stripMargin)
          .cache()
      proportion.createOrReplaceTempView("proportion")


      /**
        * this part join p1 and p2 with the distribution calculated
        */
      val result = spark.sql(
        """
          |SELECT p2.car, p2.pcode, ROUND(((to_distribute / proportion) * qtty) + qtty, 2) AS qtty
          |FROM p2
          |JOIN distribute d ON(p2.car = d.car)
          |JOIN proportion p ON(d.car = p.car)
          |UNION ALL
          |SELECT car, pcode, qtty
          |FROM p1
          |""".stripMargin)

      result.show(truncate = false)
/*
+---+-----+------+
|car|pcode|qtty  |
+---+-----+------+
|A  |444  |69.29 |
|A  |222  |27.71 |
|C  |444  |21.43 |
|C  |555  |128.57|
|A  |666  |65.0  |
|B  |555  |20.0  |
|C  |666  |70.0  |
|A  |888  |88.0  |
+---+-----+------+
*/

      expected
        .toDF("car","pcode","qtty")
        .show(truncate = false)
/*
+---+-----+------+
|car|pcode|qtty  |
+---+-----+------+
|A  |666  |65.0  |
|B  |555  |20.0  |
|A  |444  |69.29 |
|A  |222  |27.71 |
|C  |444  |21.43 |
|C  |666  |70.0  |
|C  |555  |128.57|
|A  |888  |88.0  |
+---+-----+------+
*/

    } finally {
      sc.stop()
      println("SparkContext stopped")
      spark.stop()
      println("SparkSession stopped")
    }
  }
}

使用 API 数据集

具有相同结果的此问题的另一种方法是使用Datasets和它伟大的API,作为一个例子:

import org.apache.spark.sql.SparkSession
import org.apache.log4j.{Level, Logger}
import org.apache.spark.sql.functions._
import org.apache.spark.storage.StorageLevel

object basic2 {

  val spark = SparkSession
    .builder()
    .appName("Sample app")
    .master("local")
    .config("spark.sql.shuffle.partitions","200") //Change to a more reasonable default number of partitions for our data
    .getOrCreate()

  val sc = spark.sparkContext

  final case class Owner(car: String, pcode: String, o_qtty: Double)
  final case class Invoice(car: String, pcode: String, i_qtty: Double)

  def main(args: Array[String]): Unit = {

    val data = Seq(
      Owner("A", "666", 80),
      Owner("B", "555", 20),
      Owner("A", "444", 50),
      Owner("A", "222", 20),
      Owner("C", "444", 20),
      Owner("C", "666", 80),
      Owner("C", "555", 120),
      Owner("A", "888", 100)
    )

    val fleet = Seq(
      Invoice("A", "666", 15),
      Invoice("C", "666", 10),
      Invoice("A", "888", 12),
      Invoice("B", "555", 200)
    )

    val expected = Seq(
      Owner("A", "666", 65),
      Owner("B", "555", 20), // not redistributed because produce a negative value
      Owner("A", "444", 69.29),
      Owner("A", "222", 27.71),
      Owner("C", "444", 21.43),
      Owner("C", "666", 70),
      Owner("C", "555", 128.57),
      Owner("A", "888", 88)
    )

    Logger.getRootLogger.setLevel(Level.ERROR)

    try {
      import spark.implicits._

      val owners = spark.createDataset(data)
        .as[Owner]
        .cache()

      val invoices = spark.createDataset(fleet)
        .as[Invoice]
        .cache()

      val p1 = owners
        .join(invoices,Seq("car","pcode"),"inner")
        .selectExpr("car","pcode","IF(o_qtty-i_qtty < 0,o_qtty,o_qtty - i_qtty) AS qtty","IF(o_qtty-i_qtty < 0,0,i_qtty) AS to_distribute")
        .persist(StorageLevel.MEMORY_ONLY)

      val p2 = owners
        .join(invoices,Seq("car","pcode"),"left_outer")
        .filter(row => row.anyNull == true)
        .drop(col("i_qtty"))
        .withColumnRenamed("o_qtty","qtty")
        .persist(StorageLevel.MEMORY_ONLY)

      val distribute = p1
        .groupBy(col("car"))
        .agg(sum(col("to_distribute")).as("to_distribute"))
        .persist(StorageLevel.MEMORY_ONLY)

      val proportion = p2
          .groupBy(col("car"))
          .agg(sum(col("qtty")).as("proportion"))
          .persist(StorageLevel.MEMORY_ONLY)

      val result = p2
        .join(distribute, "car")
        .join(proportion, "car")
        .withColumn("qtty",round( ((col("to_distribute") / col("proportion")) * col("qtty")) + col("qtty"), 2 ))
        .drop("to_distribute","proportion")
        .union(p1.drop("to_distribute"))

      result.show()
/*
+---+-----+------+
|car|pcode|  qtty|
+---+-----+------+
|  A|  444| 69.29|
|  A|  222| 27.71|
|  C|  444| 21.43|
|  C|  555|128.57|
|  A|  666|  65.0|
|  B|  555|  20.0|
|  C|  666|  70.0|
|  A|  888|  88.0|
+---+-----+------+
*/

      expected
        .toDF("car","pcode","qtty")
        .show(truncate = false)
/*
+---+-----+------+
|car|pcode|qtty  |
+---+-----+------+
|A  |666  |65.0  |
|B  |555  |20.0  |
|A  |444  |69.29 |
|A  |222  |27.71 |
|C  |444  |21.43 |
|C  |666  |70.0  |
|C  |555  |128.57|
|A  |888  |88.0  |
+---+-----+------+
*/

    } finally {
      sc.stop()
      println("SparkContext stopped")
      spark.stop()
      println("SparkSession stopped")
    }
  }
}

关于性能和调优的一些一般注意事项。

它始终取决于您的特定用例,但总的来说,首先,如果您可以过滤和清理数据,您可能会看到一些改进。

使用高级声明式 API 的一个重点是将自己与低级实现细节隔离。 优化是一项工作催化剂优化器 https://databricks.com/blog/2015/04/13/deep-dive-into-spark-sqls-catalyst-optimizer.html。 这是一个复杂的引擎,我真的怀疑有人可以在不深入研究其内部结构的情况下轻松对其进行改进。

默认分区数财产:spark.sql.shuffle.partitions,正确设置。

默认情况下Spark SQL uses spark.sql.shuffle.partitions数量 用于聚合和连接的分区,默认为 200。 这通常会导致分区爆炸,但没有任何影响 由于这 200 个任务(每个分区)具有查询的性能 一切都在得到结果之前开始和完成。

考虑您的查询真正需要多少个分区。

Spark只能为每个分区运行 1 个并发任务RDD,最多为集群中的核心数。 因此,如果您的集群有 50 个核心,您希望 RDD 至少有 50 个分区。 至于选择“良好”的分区数量,您通常需要至少与并行执行器的数量一样多。 您可以通过调用获得这个计算值

sc.defaultParallelism

或检查 RDD 分区数

df.rdd.partitions.size

重新分区:增加分区,过滤后重新平衡分区增加并行度repartition(numPartitions: Int)

Coalesce:在输出到 HDFS/外部之前减少分区而不进行 shuffle 合并coalesce(numPartitions: Int, suffle: Boolean = false)

您可以点击此链接:通过合并和重新分区管理 Spark 分区 https://medium.com/@mrpowers/managing-spark-partitions-with-coalesce-and-repartition-4050c57ad5c4

缓存数据以避免重新计算: dataFrame.cache()

分析器——逻辑查询计划分析器

分析器是 Spark SQL 中的逻辑查询计划分析器,在语义上 验证未解决的逻辑计划并将其转换为已分析的逻辑计划。

您可以使用解释访问数据集的分析逻辑计划(使用 启用扩展标志)

dataframe.explain(extended = true)

有关更多性能选项,请参阅文档:性能调优 https://spark.apache.org/docs/2.4.0/sql-performance-tuning.html

运行 Spark 进程有很多可能性,但这始终取决于您的用例。

批处理还是流处理?数据框还是普通 RDD?蜂巢还是不蜂巢?数据是否被打乱?等等......

我强烈推荐你Spark SQL 的内部原理 https://jaceklaskowski.gitbooks.io/mastering-spark-sql/ by 雅采克·拉斯科夫斯基.

最后,您必须使用不同的值和基准进行一些试验,以了解数据样本的处理时间。

  val start = System.nanoTime()

  // my process

  val end = System.nanoTime()

  val time = end - start
  println(s"My App takes: $time")

希望这可以帮助。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数量重新分配逻辑 - 具有外部数据集的 MapGroups 的相关文章

  • Scala 解析器组合器的运算符优先级

    我正在研究需要考虑运算符优先级的解析逻辑 我的需求并不太复杂 首先 我需要乘法和除法比加法和减法具有更高的优先级 例如 1 2 3 应视为 1 2 3 这是一个简单的例子 但你明白了 我需要将更多自定义标记添加到优先级逻辑中 我可以根据此处
  • AWS EMR Spark Python 日志记录

    我正在 AWS EMR 上运行一个非常简单的 Spark 作业 但似乎无法从我的脚本中获取任何日志输出 我尝试过打印到 stderr from pyspark import SparkContext import sys if name m
  • 在 Scala 中,使用“_”和使用命名标识符有什么区别?

    为什么当我尝试使用时会出现错误 而不是使用命名标识符 scala gt res0 res25 List Int List 1 2 3 4 5 scala gt res0 map gt item toString
  • Scalatest PlusPlay Selenium 无法调整窗口大小

    对此已经研究了一段时间 我似乎找不到使用 scalatest plus 调整窗口大小的方法 我发现在线搜索或文档的唯一方法http doc scalatest org 2 1 5 index html org scalatest selen
  • 如何在 Spark Dataframe 中显示完整的列内容?

    我正在使用 Spark csv 将数据加载到 DataFrame 中 我想做一个简单的查询并显示内容 val df sqlContext read format com databricks spark csv option header
  • 计算序列而无法存储值?

    问题陈述 here http www spoj com problems EC SER 令 S 为无限整数序列 S0 a S1 b Si Si 2 Si 1 对于所有 i gt 2 你有两个整数 a 和 b 您必须回答有关序列中第 n 个元
  • 数组所有可能的组合

    我有一个字符串数组 ted williams golden voice radio 我希望这些关键字的所有可能组合采用以下形式 ted williams golden voice radio ted williams ted golden
  • 如何使用 log4j 自定义附加程序在 HDFS 上创建日志?

    Overview 我们希望使用 log4j 记录 Spark 作业活动 并将日志文件写入 HDFS Java 8 Spark 2 4 6 Scala 2 1 2 Hadoop 3 2 1 我们无法找到本地 apache log4j 附加程序
  • 大 ר 符号到底代表什么?

    我真的很困惑大 O 大 Omega 和大 Theta 表示法之间的区别 我知道大 O 是上限 大 Omega 是下限 但是大 theta 到底代表什么 我读过这意味着紧束缚 但是 这是什么意思 首先我们来了解一下什么是大O 大Theta和大
  • Akka Streams / HTTP:从响应中获取原始请求

    我有一个 Akka Streams 源 它会遍历流程并发布 HTTP 请求 source map toRequest via Http outgoingConnection host map toMessage 假设toRequest方法将
  • 在 Scala 中调用 WebSocket 中的方法

    我是 scala Play 框架和 Akka 的新手 我的函数定义为 def socket WebSocket accept String String request gt ActorFlow actorRef out gt MyWebS
  • Java 中查看 ArrayList 是否包含对象的最有效方法

    我有一个 Java 对象的 ArrayList 这些对象有四个字段 我用其中两个字段来将对象视为与另一个对象相等 我正在寻找最有效的方法 给定这两个字段 以查看数组是否包含该对象 问题在于这些类是基于 XSD 对象生成的 因此我无法修改类本
  • 使用 Scala 进行网页抓取 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 如何访问 Scala XML 中的父元素

    The scala xml包表示带有标记树节点的 XML 但是这棵树在 Scala 2 7 中是单向的吗 因为似乎没有办法访问Elem给定的父级Elem 这似乎同样适用于父母Document 例如 在 XOM 中你有getParent an
  • Z 算法背后的直觉

    Z算法是一种复杂度为O n 的字符串匹配算法 一种用例是从字符串 B 中查找字符串 A 的最长出现次数 例如 overdose from stackoverflow 将会 over 您可以通过使用组合字符串调用 Z 算法来发现这一点 ove
  • 如何识别 Java 中的不可变对象

    在我的代码中 我正在创建一个对象集合 这些对象将由各种线程以只有在对象不可变的情况下才安全的方式访问 当尝试将新对象插入到我的集合中时 我想测试它是否是不可变的 如果不是 我将抛出异常 我能做的一件事是检查一些众所周知的不可变类型 priv
  • 我应该用不可变或可变的数据结构来表示数据库数据吗?

    我目前正在使用 Scala 进行编程 但我想这适用于任何函数式编程语言 或者更确切地说 任何建议不变性并可以与数据库交互的编程语言 当我从数据库中获取数据时 我将其映射到模型数据结构 在函数式编程中 数据结构往往是不可变的 但是数据库中的数
  • 从给定的项目列表创建子列表

    我首先要说的是以下问题不是为了家庭作业目的即使因为我几个月前就完成了软件工程师的工作 无论如何 今天我正在工作 一位朋友向我询问了这个奇怪的排序问题 我有一个包含 1000 行的列表 每行代表一个数字 我想创建 10 个子列表 每个子列表都
  • 是否有适用于 Haskell 或 Scala 等函数式语言的 LL 解析器生成器?

    我注意到明显缺乏用函数式语言创建解析器的 LL 解析器 我一直在寻找但没有成功的理想发现是为 ANTLR 风格的 LL 语法生成 Haskell 解析器 语法的模小数重新格式化 并且令我惊讶的是 每个最后一个解析器生成器都具有函数我发现的语
  • 必须包含 log4J,但它会导致 Apache Spark shell 中出现错误。如何避免错误?

    由于我必须将 jar 包含到 Spark 代码中 因此我想请求帮助找出解决此问题而不删除 log4j 导入的方法 简单代码如下 cp symjar log4j 1 2 17 jar import org apache spark rdd v

随机推荐