如何在Hive表中的Kafka主题中快速插入数据?

时间:2018-04-23 16:58:39

标签: apache-spark hive apache-kafka hdfs parquet

我有一个Kafka主题,其中我收到了大约500,000个活动。

目前,我需要将这些事件插入到Hive表中。 由于事件是时间驱动的,我决定使用以下策略:

1)在HDFS中定义路由,我称之为用户。在这条路线的内部,会有几个Parquet文件,每个文件对应一个特定的日期。例如:20180412,20180413,20180414等(格式YYYYMMDD)。 2)创建Hive表并使用格式为YYYYMMDD的日期作为分区。我们的想法是使用用户HDFS目录中的每个文件作为表的分区,只需通过以下命令添加相应的镶木地板文件:

ALTER TABLE users DROP IF EXISTS PARTITION 
(fecha='20180412') ;
ALTER TABLE users ADD PARTITION
(fecha='20180412') LOCATION '/users/20180412';

3)通过从最早的事件迭代来读取Kafka主题中的数据,在事件中获取日期值(在参数 dateClient 中),并给出该日期值,将值插入相应的Parque文件。 4)为了完成第3点,我读取每个事件并将其保存在临时HDFS文件中,我使用Spark来读取文件。之后,我使用Spark将临时文件内容转换为数据框。 5)使用Spark,我设法将DataFrame值插入到Parquet文件中。

代码遵循这种方法:

val conf = ConfigFactory.parseResources("properties.conf")
val brokersip = conf.getString("enrichment.brokers.value")
val topics_in = conf.getString("enrichment.topics_in.value")
val spark = SparkSession
    .builder()
    .master("yarn")
    .appName("ParaTiUserXY")
    .getOrCreate()

spark.sparkContext.setLogLevel("ERROR")
import spark.implicits._

val properties = new Properties
properties.put("key.deserializer", classOf[StringDeserializer])
properties.put("value.deserializer", classOf[StringDeserializer])
properties.put("bootstrap.servers", brokersip)
properties.put("auto.offset.reset", "earliest")
properties.put("group.id", "UserXYZ2")

//Schema para transformar los valores del topico de Kafka a JSON
val my_schema = new StructType()
    .add("longitudCliente", StringType)
    .add("latitudCliente", StringType)
    .add("dni", StringType)
    .add("alias", StringType)
    .add("segmentoCliente", StringType)
    .add("timestampCliente", StringType)
    .add("dateCliente", StringType)
    .add("timeCliente", StringType)
    .add("tokenCliente", StringType)
    .add("telefonoCliente", StringType)

val consumer = new KafkaConsumer[String, String](properties)
consumer.subscribe( util.Collections.singletonList("geoevents") )

val fs = {
    val conf = new Configuration()
    FileSystem.get(conf)
}

val temp_path:Path = new Path("hdfs:///tmp/tmpstgtopics")
    if( fs.exists(temp_path)){
        fs.delete(temp_path, true)
}

while(true)
{
    val records=consumer.poll(100)
    for (record<-records.asScala){
        val data = record.value.toString
        val dataos: FSDataOutputStream = fs.create(temp_path)
        val bw: BufferedWriter = new BufferedWriter( new OutputStreamWriter(dataos, "UTF-8"))
        bw.append(data)
        bw.close
        val data_schema = spark.read.schema(my_schema).json("hdfs:///tmp/tmpstgtopics")
        val fechaCliente = data_schema.select("dateCliente").first.getString(0)

        if( fechaCliente < date){
            data_schema.select("longitudCliente", "latitudCliente","dni", "alias", 
            "segmentoCliente", "timestampCliente", "dateCliente", "timeCliente", 
            "tokenCliente", "telefonoCliente").coalesce(1).write.mode(SaveMode.Append)
            .parquet("/desa/landing/parati/xyusers/" + fechaCliente)
          }
          else{
              break
          }
        }
    }

  consumer.close()

但是,此方法大约需要1秒钟来处理群集中的每条记录。到目前为止,这意味着我需要大约6天的时间来处理我所有的事件。

这是将Kafka主题中的全部事件插入Hive表的最佳方式吗?

还有哪些其他替代方案或我可以对我的代码进行哪些升级以加快速度?

2 个答案:

答案 0 :(得分:2)

除了您没有正确使用Spark Streaming从Kafka进行轮询(您使用while循环编写了一个香草Scala Kafka消费者)这一事实之外,coalesce(1)将永远是一个瓶颈因为它强制一个执行人收集记录,我只是说你真的在这里重新发明轮子。

  

还有其他替代方案

我所知道的并且都是开源的

  • Gobblin(取代Camus)由LinkedIn
  • Kafka Connect w / HDFS Sink Connector(内置于Confluent Platform,但也是从Github上的源代码构建)
  • Streamsets
  • Apache NiFi
  • Secor by Pinterest

根据列出的内容,您可以使用JSON或Avro编码的Kafka消息,而不是扁平字符串。这样,您可以将文件原样放入Hive serde中,而不是在使用它们时解析它们。如果您无法编辑生产者代码,请创建单独的Kafka Streams作业,获取原始字符串数据,解析它,然后写入Avro或JSON的新主题。

如果您选择Avro(您真的应该支持Hive),则可以使用Confluent Schema Registry。或者,如果您正在运行Hortonworks,他们会提供类似的注册表。

在Avro上运行远远优于文本或JSON。 Avro可以轻松转换为Parquet,我相信上述每个选项至少提供Parquet支持,而其他选项也可以进行ORC(Kafka Connect目前不做ORC)。

上述每一项都支持基于Kafka记录时间生成某种级别的自动Hive分区。

答案 1 :(得分:0)

您可以通过增加kafka主题的分区并让一个或多个具有多个使用者的消费者组与每个分区一对一消费来改善并行性。

正如,cricket_007提到的,您可以使用其中一个开源框架,或者您可以让更多的消费者组使用相同的主题来卸载数据。