【问题标题】:Spark Structured Streaming with HBase Sink使用 HBase Sink 的 Spark 结构化流
【发布时间】:2020-10-22 17:02:16
【问题描述】:

我的用例是使用结构化流读取 Kafka 消息,并使用 foreachBatch 将这些消息推送到 HBase,方法是使用一些批量 Put 以获得比单个 Put 更高的性能,我可以使用 foreach 推送消息(感谢 Spark Structured Streaming with Hbase integration ) 但不能对 foreachBatch 操作做同样的事情。

有人可以帮忙吗?附上以下代码。

KafkaStructured.scala:


package com.test

import java.math.BigInteger
import java.util

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import org.apache.hadoop.hbase.client.Put
import org.apache.hadoop.hbase.util.Bytes
import org.apache.spark.sql._
import org.apache.spark.sql.functions._


object KafkaStructured {

  @JsonIgnoreProperties(ignoreUnknown = true)
  case class Header(field1: String, field2: String, field3: String)

  @JsonIgnoreProperties(ignoreUnknown = true)
  case class Body(fieldx: String)

  @JsonIgnoreProperties(ignoreUnknown = true)
  case class Event(header: Header, body: Body)

  @JsonIgnoreProperties(ignoreUnknown = true)
  case class KafkaResp(event: Event)

  @JsonIgnoreProperties(ignoreUnknown = true)
  case class HBaseDF(field1: String, field2: String, field3: String)


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

    val jsonSchema = Encoders.product[KafkaResp].schema

    val spark = SparkSession
      .builder()
      .appName("Kafka Spark")
      .getOrCreate()

    val df = spark
      .readStream
      .format("kafka")
      .option...
      .load()

    import spark.sqlContext.implicits._

    val flattenedDf: DataFrame =
      df
        .select($"value".cast("string").as("json"))
        .select(from_json($"json", jsonSchema).as("data"))
        .select("data.event.header.field1", "data.event.header.field2", "data.event.header.field3")

    val hbaseDf = flattenedDf
      .as[HBaseDF]
      .filter(hbasedf => hbasedf != null && hbasedf.field1 != null)

    flattenedDf
      .writeStream
      .option("truncate", "false")
      .option("checkpointLocation", "some hdfs location")
      .format("console")
      .outputMode("append")
      .start()

    def bytes(data: String) = {
      val bytes = data match {
        case data if data != null && !data.isEmpty => Bytes.toBytes(data)
        case _ => Bytes.toBytes("")
      }
      bytes
    }

   
    hbaseDf
      .writeStream
      .foreachBatch(function = (batchDf, batchId) => {
        val putList = new util.ArrayList[Put]()
        batchDf
          .foreach(row => {
            val p: Put = new Put(bytes(row.field1))
            val cfName= bytes("fam1")
            p.addColumn(cfName, bytes("field1"), bytes(row.field1))
            p.addColumn(cfName, bytes("field2"), bytes(row.field2))
            p.addColumn(cfName, bytes("field3"), bytes(row.field3))
            putList.add(p)
          })
        new HBaseBulkForeachWriter[HBaseDF] {
          override val tableName: String = "<my table name>"
        
          override def bulkPut: util.ArrayList[Put] = {
            putList
          }
        }
      }
      )
      .start()

    spark.streams.awaitAnyTermination()
  }

}

HBaseBulkForeachWriter.scala:


package com.test

import java.util
import java.util.concurrent.ExecutorService

import org.apache.hadoop.hbase.client.{Connection, ConnectionFactory, Put, Table}
import org.apache.hadoop.hbase.security.User
import org.apache.hadoop.hbase.{HBaseConfiguration, TableName}
import org.apache.spark.sql.ForeachWriter

import scala.collection.mutable

trait HBaseBulkForeachWriter[RECORD] extends ForeachWriter[RECORD] {

  val tableName: String
  val hbaseConfResources: mutable.Seq[String] = mutable.Seq("location for core-site.xml", "location for hbase-site.xml")

  def pool: Option[ExecutorService] = None

  def user: Option[User] = None

  private var hTable: Table = _
  private var connection: Connection = _

  override def open(partitionId: Long, version: Long): Boolean = {
    connection = createConnection()
    hTable = getHTable(connection)
    true
  }

  def createConnection(): Connection = {
    val hbaseConfig = HBaseConfiguration.create()
    hbaseConfResources.foreach(hbaseConfig.addResource)
    ConnectionFactory.createConnection(hbaseConfig, pool.orNull, user.orNull)
  }

  def getHTable(connection: Connection): Table = {
    connection.getTable(TableName.valueOf(tableName))
  }

  override def process(record: RECORD): Unit = {
    val put = bulkPut
    hTable.put(put)
  }

  override def close(errorOrNull: Throwable): Unit = {
    hTable.close()
    connection.close()
  }

  def bulkPut: util.ArrayList[Put]
}

【问题讨论】:

    标签: scala spark-streaming spark-structured-streaming


    【解决方案1】:

    foreachBatch 允许您在函数内部使用 foreachPartition。 在foreachPartition 中执行的代码每个执行程序只运行一次。

    所以你可以创建一个函数来创建一个put:

    def putValue(key: String, columnName: String, data: Array[Byte]): Put = {
        val put = new Put(Bytes.toBytes(key))
        put.addColumn(Bytes.toBytes("colFamily"), Bytes.toBytes(columnName), data)
      }
    

    然后是批量插入 puts 的函数

    def writePutList(putList: List[Put]): Unit = {
        val config: Configuration = HBaseConfiguration.create()
        config.set("hbase.zookeeper.quorum", zookeperUrl)
    
        val connection: Connection = ConnectionFactory.createConnection(config)
        val table = connection.getTable(TableName.valueOf(tableName))
        table.put(putList.asJava)
        logger.info("INSERT record[s] " + putList.size + " to table " + tableName + " OK.")
        table.close()
        connection.close()
      }
       
    

    并在foreachPartition 和map 中使用它们

     def writeFunction: (DataFrame, Long) => Unit = {
        (batchData, id) => {
          batchData.foreachPartition(
            partition => {  
              val putList = partition.map(
                data =>
                 putValue(data.getAs[String]("keyField"), "colName", Bytes.toBytes(data.getAs[String]("valueField")))
              ).toList
             writePutList(putList)
            }
          )
        }
      }
    

    最后使用在您的流式查询中创建的函数:

     df.writeStream
          .queryName("yourQueryName")
          .option("checkpointLocation", checkpointLocation)
          .outputMode(OutputMode.Update())
          .foreachBatch(writeFunction)
          .start()
          .awaitTermination()
    

    【讨论】:

      猜你喜欢
      • 2018-04-19
      • 2020-04-28
      • 1970-01-01
      • 1970-01-01
      • 2023-03-21
      • 2019-09-05
      • 1970-01-01
      • 1970-01-01
      • 2017-05-04
      相关资源
      最近更新 更多