生成的实体的Spark ETL唯一标识符

时间:2018-03-28 12:11:21

标签: apache-spark design-patterns apache-spark-sql spark-dataframe etl

我们在Spark中有一个要求,即来自Feed的每条记录都会被分成一组。 示例server.port=8089 server.ssl.enabled=true server.ssl.key-store=src/main/resources/keystore.p12 server.ssl.key-store-password=**** server.ssl.keyStoreType=PKCS12 server.ssl.keyAlias=tomcat java.io.IOException: Alias name tomcat does not identify a key entry at org.apache.tomcat.util.net.jsse.JSSESocketFactory.getKeyManagers(JSSESocketFactory.java:596) at org.apache.tomcat.util.net.jsse.JSSESocketFactory.getKeyManagers(JSSESocketFactory.java:534) at org.apache.tomcat.util.net.NioEndpoint.bind(NioEndpoint.java:363) at org.apache.tomcat.util.net.AbstractEndpoint.start(AbstractEndpoint.java:739) at org.apache.coyote.AbstractProtocol.start(AbstractProtocol.java:472) at org.apache.coyote.http11.Http11NioProtocol.start(Http11NioProtocol.java:81) at org.apache.catalina.connector.Connector.startInternal(Connector.java:986) {col1,col2,col3}=>Resource等。

现在我需要在ETL层中生成一个唯一标识符,它可以分别持久保存到上述每个表/实体的数据库表中。

此唯一标识符用于查找值以标识每个表记录并在数据库中生成序列。

  1. First Approach使用Redis键为使用Feed中的Natural Unique列标识的每个实体生成键。 但是这种方法并不稳定,因为redis在高峰时段使用了崩溃,而redis在单线程模式下运行。当我运行太多的etl作业时它会很慢。
  2. 我的想法是使用像SHA256这样的Crypto Alghorithm,而不是Sha32算法有32位,有不同值的哈希冲突的可能性。因为SHA256有更多位,所以哈希值的范围= 2 ^ 64 所以HashCollision的可能性非常小,因为SHA256使用了4bit的分组密码进行加密。
  3. 但是第二种选择并没有被很多人所接受。 在ETL层中创建唯一键的其他选项/解决方案有哪些,可以在DB中查找以进行比较。

    先谢谢, Rajesh Giriayppa

4 个答案:

答案 0 :(得分:1)

使用数据帧,您可以使用“生成单调增加的64位整数”20函数(https://spark.apache.org/docs/2.1.0/api/scala/index.html#org.apache.spark.sql.functions $)。它可以这样使用:

monotonicallyIncreasingId

使用RDD,您可以使用dataframe.withColumn("INDEX", functions.monotonicallyIncreasingId()) zipWithIndex。前者生成一个真实的索引(在0和N-1之间排序,N是RDD的大小),而后者生成唯一的长ID,而没有进一步的保证,这似乎是你需要的(https://spark.apache.org/docs/2.1.0/api/scala/index.html#org.apache.spark.rdd.RDD)。请注意,zipWithUniqueId甚至不会触发火花作业,因此几乎是免费的。

答案 1 :(得分:1)

感谢您的回复,我尝试过这种方法,它不会给我相关或代理主键来搜索数据库。每次运行etl作业索引或数字对于每条记录都会有所不同,如果我的数据集计数变化。 我需要唯一的我要与dB记录相关联,它只匹配一条记录,并且应该是以dB为单位的同一记录。

是否有任何好的设计模式或做法可以将etl数据集行与dB记录进行比较,并且具有唯一的I'。

答案 2 :(得分:1)

这有点晚了,但是如果有人在找...

我遇到了类似的要求。如Oli先前所述,zipWithIndex将给出顺序的,零索引的ID,然后您可以将其映射到偏移量。请注意,其中有一个关键部分,因此根据使用情况,可能需要锁定机制。

case class Resource(_1: String, _2: String, _3: String, id: Option[Long])
case class Account(_4: String, _5: String, _6: String, id: Option[Long])

val inDS = Seq(
  ("a1", "b1", "c1", "x1", "y1", "z1"), 
  ("a2", "b2", "c2", "x2", "y2", "z2"),
  ("a3", "b3", "c3", "x3", "y3", "z3")).toDS()

val offset = 1001 // load actual offset from db

val withSeqIdsDS = inDS.map(x => (Resource(x._1, x._2, x._3, None), Account(x._4, x._5, x._6, None)))
  .rdd.zipWithIndex // map index from 0 to n-1
  .map(x => (
    x._1._1.copy(id = Option(offset + x._2 * 2)),
    x._1._2.copy(id = Option(offset + x._2 * 2 + 1))
  )).toDS()

// save new offset to db

withSeqIdsDS.show()
+---------------+---------------+
|             _1|             _2|
+---------------+---------------+
|[a1,b1,c1,1001]|[x1,y1,z1,1002]|
|[a2,b2,c2,1003]|[x2,y2,z2,1004]|
|[a3,b3,c3,1005]|[x3,y3,z3,1006]|
+---------------+---------------+

withSeqIdsDS.select("_1.*", "_2.*").show
+---+---+---+----+---+---+---+----+
| _1| _2| _3|  id| _4| _5| _6|  id|
+---+---+---+----+---+---+---+----+
| a1| b1| c1|1001| x1| y1| z1|1002|
| a2| b2| c2|1003| x2| y2| z2|1004|
| a3| b3| c3|1005| x3| y3| z3|1006|
+---+---+---+----+---+---+---+----+

答案 3 :(得分:0)

以下内容可以帮助你@Rajesh吗?

    import org.apache.spark.SparkConf
    import org.apache.spark.sql.SparkSession

    val sparkConf = new SparkConf().setAppName("hash-to-db").setMaster("local")
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()

    import org.apache.spark.sql.functions._
    import spark.implicits._
    var dfA = spark.createDataset(Seq(
      (4, "01/01/2017"),
      (2, "03/01/2017"),
      (6, "04/01/2017"),
      (1, "05/01/2017"),
      (5, "09/01/2017"),
      (3, "02/01/2017"),
      (11, "01/01/2017"),
      (12, "03/01/2017"),
      (16, "04/01/2017"),
      (21, "05/01/2017"),
      (35, "09/01/2017"),
      (13, "02/01/2017")
    )).toDF("id", "date")


  dfA
  .withColumn("db_id", sha2(concat($"id", $"date"), 256))
  .toDF(cols: _*).show()

   dfA.foreachPartition {
      iter => /* update db here*/
   }

最初我们有一个带有('id','date')的数据框,我们假设它是你的一个表。接下来使用withColumn("db_id", concat($"id", $"date"))我们生成一个新列,我们通过连接id + date来调用 db_id ,这将是哈希的提要。

map函数将根据db_id中的SHA2算法生成一个哈希值。

最后在foreachPartition中,您可以将db_id和更多内容存储到数据库中。

这是我的样本数据的输出:

+---+----------+--------------------+
| id|      date|               db_id|
+---+----------+--------------------+
|  4|01/01/2017|6e8480d61a3c10dc6...|
|  2|03/01/2017|6274256111354de0a...|
|  6|04/01/2017|da1df6398a3d1cd1b...|
|  1|05/01/2017|7eee447fea5a0c617...|
|  5|09/01/2017|91ba5395428b9c020...|
|  3|02/01/2017|d645e6e7f3ea32143...|
| 11|01/01/2017|1d8efa87c0314d163...|
| 12|03/01/2017|f73fc571d46cb8ce8...|
| 16|04/01/2017|f68486fdb68499f21...|
| 21|05/01/2017|8d83f80492170d8e9...|
| 35|09/01/2017|66e99f71ba22d97e2...|
| 13|02/01/2017|996ae110f1a2fc855...|
+---+----------+--------------------+

如果您完全确定记录的唯一性,则可能决定仅保留连接部分并跳过哈希。

祝你好运