使用结构化Spark流在HBase中批量插入数据

时间:2019-05-24 13:49:54

标签: scala apache-spark hbase spark-streaming bulkinsert

我正在使用结构化火花流读取来自Kafka(每秒100.000行)的数据,并且我正在尝试将所有数据插入HBase。

我使用的是Cloudera Hadoop 2.6,使用的是Spark 2.3

我尝试了类似看过here的事情。

eventhubs.writeStream
 .foreach(new MyHBaseWriter[Row])
 .option("checkpointLocation", checkpointDir)
 .start()
 .awaitTermination()

MyHBaseWriter看起来像这样:

class AtomeHBaseWriter[RECORD] extends HBaseForeachWriter[Row] {
  override def toPut(record: Row): Put = {
    override val tableName: String = "hbase-table-name"

    override def toPut(record: Row): Put = {
        // Get Json
        val data = JSON.parseFull(record.getString(0)).asInstanceOf[Some[Map[String, Object]]]
        val key = data.getOrElse(Map())("key")+ ""
        val val = data.getOrElse(Map())("val")+ ""

        val p = new Put(Bytes.toBytes(key))
        //Add columns ... 
        p.addColumn(Bytes.toBytes(columnFamaliyName),Bytes.toBytes(columnName), Bytes.toBytes(val))

        p
     }
    }

HBaseForeachWriter类如下所示:

trait HBaseForeachWriter[RECORD] extends ForeachWriter[RECORD] {
  val tableName: String

  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 = {
    // I create HBase Connection Here
  }

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

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

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

  def toPut(record: RECORD): Put
}

因此,在这里我逐行进行放置,即使我每个都允许20个执行器和4个内核,我也没有立即将数据插入到HBase中。因此,我需要做的是大量工作,因为我在互联网上发现的所有东西都是使用RDD和Map / Reduce实现的。

1 个答案:

答案 0 :(得分:2)

我了解的是hbase中记录的吸收速度很慢。我对您没有什么建议。

1)hbase.client.write.buffe r
 以下属性可能会对您有所帮助。

hbase.client.write.buffer
     

描述 。BufferedMutator写缓冲区的默认大小(以字节为单位)。较大的缓冲区会占用更多内存-在客户端和客户端   服务器端,因为服务器将传递的写缓冲区实例化到   处理它-但较大的缓冲区大小会减少制作的RPC的数量。   对于服务器端使用的内存的估计,请评估   hbase.client.write.buffer * hbase.regionserver.handler.count

     

默认2097152(大约2 mb)

我更喜欢foreachBatch see spark docs(在spark核心中是foreachPartition)而不是foreach

还在您的hbase编写器中扩展了ForeachWriter

open方法初始化put的数组列表 在process中,将看跌期权添加到看跌期权的数组列表中 close table.put(listofputs);中,然后在更新表后重置arraylist ...

基本上,上面提到的缓冲区大小填充为2 mb,然后它将刷新到hbase表中。在那之前,记录将不会进入hbase表。

您可以将其增加到10mb,等等。 这样,RPC的数量将减少。大量数据将被刷新并将存储在hbase表中。

当写缓冲区已满并且触发flushCommits到hbase表中时。

示例代码:在我的answer

2)关闭WAL 您可以关闭WAL(预写日志-危险无法恢复),但是如果不想这样做,它将加快写入速度。恢复数据。

  

注意:如果您在hbase表上使用solr或cloudera搜索,   不应将其关闭,因为Solr可以在WAL上使用。如果您切换它   然后,Solr索引将无法工作..这是一个常见错误,许多   我们做到了。

如何关闭: https://hbase.apache.org/1.1/apidocs/org/apache/hadoop/hbase/client/Put.html#setWriteToWAL(boolean)

就像我提到的puts列表是个好方法...这是在结构化流示例之前执行的旧方法(带有puts列表的foreachPartition),如下所示。其中foreachPartition为每个分区而不是每个分区运行行。

def writeHbase(mydataframe: DataFrame) = {
      val columnFamilyName: String = "c"
      mydataframe.foreachPartition(rows => {
        val puts = new util.ArrayList[ Put ]
        rows.foreach(row => {
          val key = row.getAs[ String ]("rowKey")
          val p = new Put(Bytes.toBytes(key))
          val columnV = row.getAs[ Double ]("x")
          val columnT = row.getAs[ Long ]("y")
          p.addColumn(
            Bytes.toBytes(columnFamilyName),
            Bytes.toBytes("x"),
            Bytes.toBytes(columnX)
          )
          p.addColumn(
            Bytes.toBytes(columnFamilyName),
            Bytes.toBytes("y"),
            Bytes.toBytes(columnY)
          )
          puts.add(p)
        })
        HBaseUtil.putRows(hbaseZookeeperQuorum, hbaseTableName, puts)
      })
    }
  

总结:

     

我的感觉是我们需要了解spark和hbase的心理学   从而形成有效的配对。