我们知道,HBase 为我们提供了 hbase-mapreduce 工程包含了读取 HBase 表的 InputFormatOutputFormat 等类。这个工程的描述如下:
This module contains implementations of InputFormat, OutputFormat, Mapper, Reducer, etc which are needed for running MR jobs on tables, WALs, HFiles and other HBase specific constructs. It also contains a bunch of tools: RowCounter, ImportTsv, Import, Export, CompactionTool, ExportSnapshot, WALPlayer, etc.
我们也知道,虽然上面描述的是 MR jobs,但是 Spark 也是可以使用这些 InputFormatOutputFormat 来读写 HBase 表的,如下:

val sparkSession = SparkSession.builder
  .appName("HBase")
  .getOrCreate()
 
val conf = HBaseConfiguration.create()
conf.set("hbase.zookeeper.quorum", "https://www.iteblog.com:2181")
conf.set(TableInputFormat.INPUT_TABLE, "iteblog")
 
val HBaseRdd = sparkSession.sparkContext.newAPIHadoopRDD(conf, classOf[TableInputFormat],
  classOf[ImmutableBytesWritable],
  classOf[Result])
 
println(HBaseRdd.count())

上面程序使用 TableInputFormat 计算了 iteblog 表的总行数。如果我们想查询某个 UID 的所有历史记录如何实现呢?如果你查看 TableInputFormat 代码,你会发现其包含了很大参数设置:

hbase.mapreduce.inputtable
hbase.mapreduce.splittable
hbase.mapreduce.scan
hbase.mapreduce.scan.row.start
hbase.mapreduce.scan.row.stop
hbase.mapreduce.scan.column.family
hbase.mapreduce.scan.columns
hbase.mapreduce.scan.timestamp
hbase.mapreduce.scan.timerange.start
hbase.mapreduce.scan.timerange.end
hbase.mapreduce.scan.maxversions
hbase.mapreduce.scan.cacheblocks
hbase.mapreduce.scan.cachedrows
hbase.mapreduce.scan.batchsize
hbase.mapreduce.inputtable.shufflemaps

其中 hbase.mapreduce.inputtable 就是需要查询的表,也就是上面 Spark 程序里面的 TableInputFormat.INPUT_TABLE。而 hbase.mapreduce.scan.row.start 和 hbase.mapreduce.scan.row.stop 分别对应的是需要查询的起止 Rowkey,所以我们可以利用这个信息来实现某个范围的数据查询。但是要注意的是,iteblog 这张表是加盐了,所以我们需要在 UID 之前加上一些前缀,否则是查询不到数据的。不过 TableInputFormat 并不能实现这个功能。那如何处理呢?答案是重写 TableInputFormat 的 getSplits 方法。

从名字也可以看出 getSplits 是计算有多少个 Splits。在 HBase 中,一个 Region 对应一个 Split,对应于 TableSplit 实现类。TableSplit 的构造是需要传入 startRow 和 endRowstartRow 和 endRow 对应的就是上面 hbase.mapreduce.scan.row.start 和 hbase.mapreduce.scan.row.stop 参数传进来的值,所以如果我们需要处理加盐表,就需要在这里实现。

另一方面,我们可以通过 RegionLocator 的 getStartEndKeys() 拿到某张表所有 Region 的 StartKeys 和 EndKeys 的。然后将拿到的 StartKey 和用户传进来的 hbase.mapreduce.scan.row.start 和 hbase.mapreduce.scan.row.stop 值进行拼接即可实现我们要的需求。根据这个思路,我们的代码就可以按照如下实现:

package com.iteblog.data.spark;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
 
import com.google.common.base.Strings;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.RegionLocator;
import org.apache.hadoop.hbase.mapreduce.TableInputFormat;
import org.apache.hadoop.hbase.mapreduce.TableSplit;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.JobContext;
 
public class SaltRangeTableInputFormat extends TableInputFormat {
 
    @Override
    public List<InputSplit> getSplits(JobContext context) throws IOException {
        Configuration conf = context.getConfiguration();
 
        String tableName = conf.get(TableInputFormat.INPUT_TABLE);
        if (Strings.isNullOrEmpty(tableName)) {
            throw new IOException("tableName must be provided.");
        }
 
        Connection connection = ConnectionFactory.createConnection(conf);
        val table = TableName.valueOf(tableName)
        RegionLocator regionLocator = connection.getRegionLocator(table);
 
 
        String scanStart = conf.get(TableInputFormat.SCAN_ROW_START);
        String scanStop = conf.get(TableInputFormat.SCAN_ROW_STOP);
 
        Pair<byte[][], byte[][]> keys = regionLocator.getStartEndKeys();
        if (keys == null || keys.getFirst() == null || keys.getFirst().length == 0) {
            throw new RuntimeException("At least one region is expected");
        }
        List<InputSplit> splits = new ArrayList<>(keys.getFirst().length);
        for (int i = 0; i < keys.getFirst().length; i++) {
            String regionLocation = getTableRegionLocation(regionLocator, keys.getFirst()[i]);
            String regionSalt = null;
            if (keys.getFirst()[i].length > 0) {
                regionSalt = Bytes.toString(keys.getFirst()[i]).split("-")[0];
            }
 
            byte[] startRowKey = Bytes.toBytes(regionSalt + "-" + scanStart);
            byte[] endRowKey = Bytes.toBytes(regionSalt + "-" + scanStop);
 
            InputSplit split = new TableSplit(TableName.valueOf(tableName),
                    startRowKey, endRowKey, regionLocation);
            splits.add(split);
        }
        return splits;
    }
 
    private String getTableRegionLocation(RegionLocator regionLocator,
                                          byte[] rowKey) throws IOException {
        return regionLocator.getRegionLocation(rowKey).getHostname();
    }
}

然后我们同样查询 UID = 1000 的用户所有历史记录,那么我们的程序可以如下实现:

package com.iteblog.data.spark
 
import org.apache.hadoop.hbase.HBaseConfiguration
import org.apache.hadoop.hbase.client.Result
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce.TableInputFormat
import org.apache.hadoop.hbase.util.Bytes
import org.apache.spark.sql.SparkSession
 
import scala.collection.JavaConversions._
 
object Spark {
  def main(args: Array[String]): Unit = {
    val sparkSession = SparkSession.builder
      .appName("HBase")
      .getOrCreate()
 
    val conf = HBaseConfiguration.create()
    conf.set("hbase.zookeeper.quorum", "https://www.iteblog.com:2181")
    conf.set(TableInputFormat.INPUT_TABLE, "iteblog")
    conf.set(TableInputFormat.SCAN_ROW_START, "1000")
    conf.set(TableInputFormat.SCAN_ROW_STOP, "1001")
 
    val HBaseRdd = sparkSession.sparkContext.newAPIHadoopRDD(conf, classOf[SaltRangeTableInputFormat],
      classOf[ImmutableBytesWritable],
      classOf[Result])
 
    HBaseRdd.foreach { case (_, result) =>
      val rowKey = Bytes.toString(result.getRow)
      val cell = result.listCells()
      cell.foreach { item =>
        val family = Bytes.toString(item.getFamilyArray, item.getFamilyOffset, item.getFamilyLength)
        val qualifier = Bytes.toString(item.getQualifierArray,
          item.getQualifierOffset, item.getQualifierLength)
        val value = Bytes.toString(item.getValueArray, item.getValueOffset, item.getValueLength)
        println(rowKey + " \t " + "column=" + family + ":" + qualifier + ", " +
          "timestamp=" + item.getTimestamp + ", value=" + value)
      }
    }
  }
}

我们编译打包上面的程序,然后使用下面命令运行上述程序:

bin/spark-submit --class com.iteblog.data.spark.Spark
                 --master yarn
                 --deploy-mode cluster
                 --driver-memory 2g
                 --executor-memory 2g ~/hbase-1.0-SNAPSHOT.jar

得到的结果如下:

A-1000-1550572395399     column=f:age, timestamp=1549091990253, value=54
A-1000-1550572395399     column=f:uuid, timestamp=1549091990253, value=e9b10a9f-1218-43fd-bd01
A-1000-1550572413799     column=f:age, timestamp=1549092008575, value=4
A-1000-1550572413799     column=f:uuid, timestamp=1549092008575, value=181aa91e-5f1d-454c-959c
A-1000-1550572414761     column=f:age, timestamp=1549092009531, value=33
A-1000-1550572414761     column=f:uuid, timestamp=1549092009531, value=19aad8d3-621a-473c-8f9f
B-1000-1550572388491     column=f:age, timestamp=1549091983276, value=1
B-1000-1550572388491     column=f:uuid, timestamp=1549091983276, value=cf720efe-2ad2-48d6-81b8
B-1000-1550572392922     column=f:age, timestamp=1549091987701, value=7
B-1000-1550572392922     column=f:uuid, timestamp=1549091987701, value=8a047118-e130-48cb-adfe
.....

和前面文章使用 HBase Shell 输出结果一致。

HBase 中加盐(Salting)之后的表如何读取:Spark 篇的更多相关文章

  1. HBase 中加盐之后的表如何读取:Spark 篇

    在 <HBase 中加盐之后的表如何读取:协处理器篇> 文章中介绍了使用协处理器来查询加盐之后的表,本文将介绍第二种方法来实现相同的功能. 我们知道,HBase 为我们提供了 hbase- ...

  2. HBase中加盐(Salting)之后的表如何读取:协处理器文章

    我们介绍了避免数据斑点的三种比较常见方法: 加盐-盐腌 哈希-散列 反转-反转 其中在加盐(Salting)的方法里面是这么描述的:给Rowkey分配一个随机指针以使其和之前排序不同.但是在Rowke ...

  3. hbase数据加盐(Salting)存储与协处理器查询数据的方法

    转自: https://blog.csdn.net/finad01/article/details/45952781 ----------------------------------------- ...

  4. MD5加密算法中的加盐值 ,和彩虹表攻击 防止彩虹表撞库

    一.什么是彩虹表? 彩虹表(Rainbow Tables)就是一个庞大的.针对各种可能的字母组合预先计算好的哈希值的集合,不一定是针对MD5算法的,各种算法的都有,有了它可以快速的破解各类密码.越是复 ...

  5. hive和hbase本质区别——hbase本质是OLTP的nosql DB,而hive是OLAP 底层是hdfs,需从已有数据库同步数据到hdfs;hive可以用hbase中的数据,通过hive表映射到hbase表

    对于hbase当前noSql数据库的一种,最常见的应用场景就是采集的网页数据的存储,由于是key-value型数据库,可以再扩展到各种key-value应用场景,如日志信息的存储,对于内容信息不需要完 ...

  6. abp架构中加载公共css样式表和公共js的文件目录位置

    src\shared\helpers\LocalizedResourcesHelper.ts

  7. 将HBase中的表加载到hive中

    两种方式加载hbase中的表到hive中,一是hive创建外部表关联hbase表数据,二是hive创建普通表将hbase的数据加载到本地 1. 创建外部表 hbase中已经有了一个test表,内容如下 ...

  8. [Phoenix] 四、加盐表

    摘要: 在密码学中,加盐是指在散列之前将散列内容(例如:密码)的任意固定位置插入特定的字符串.这个在散列中加入字符串的方式称为“加盐”.其作用是让加盐后的散列结果和没有加盐的结果不相同,在不同的应用情 ...

  9. 一种简单的md5加盐加密的方法(防止彩虹表撞库)

    md5加密(或者说摘要算法)大家都很熟悉了 就不解释了 现在很多数据库设计都喜欢用单向加密的方式保存密码,验证时对提交的密码再次加密之后做密文对比 /// <summary> 使用MD5加 ...

随机推荐

  1. 仙人掌图判定及求直径HDU3594 BZOJ1023

    https://wenku.baidu.com/view/ce296043192e45361066f575.html   //仙人掌图基础知识3个判定条件 http://blog.csdn.net/y ...

  2. JUC整理笔记一之细说Unsafe

    JUC(java.util.concurrent)的开始,可以说是从Unsafe类开始. Unsafe 简介 Unsafe在sun.misc 下,顾名思义,这是一个不安全的类,因为Unsafe类所操作 ...

  3. C++98/11/17表达式类别

    目标 以下代码能否编译通过,能否按照期望运行?(点击展开) #include <utility> #include <type_traits> namespace cpp98 ...

  4. java-五大内存图

    jrm—Java虚拟机在进行程序运行时会向cpu申请一个内存约为10%左右,该内存被jrm分为5大区域 一:栈内存(stack)用来存储变量 当栈消失时,变量也随之消失.二:堆内存(heap)在Jav ...

  5. 阿里P9精心编写高并发设计手册,来看大厂是如何进行系统设计

    在看这篇文章的应该都是IT圈的朋友吧,不知道你们有没有考虑过这样几件事: 淘宝双11的剁手狂欢为什么天猫没崩掉? 为什么滴滴打车高峰如何滴滴依旧可以平稳运行? 为什么疫情期间,钉钉能支撑那么多人同时上 ...

  6. JavaScript 实现 冒泡排序

        <script>         //数组排序(冒泡排序)         //冒泡排序是一种算法,把一系列的数据按照一定的循序进行排列显示(从小到大或从大到小)          ...

  7. 2018京东校招Java笔试题

    相比阿里巴巴,京东的题都是考研基础题,加上一点java基础知识和linux命令. 1. 单选题(19道题,每题2分): 1)4个并发进程都需要5个同类资源,则至少需要多少个资源,才不会导致死锁? 2) ...

  8. 【Java8新特性】不了解Optional类,简历上别说你懂Java8!!

    写在前面 最近,很多读者出去面试都在Java8上栽了跟头,事后自己分析,确实对Java8的新特性一知半解.然而,却在简历显眼的技能部分写着:熟练掌握Java8的各种新特性,能够迅速使用Java8开发高 ...

  9. 关于hexo-blog-encrypt插件输入密码后无响应的问题

    解决方案:更改网站为https协议 具体请查看: https://github.com/MikeCoder/hexo-blog-encrypt/issues/114

  10. Jmeter(五) - 从入门到精通 - 创建网络计划实战和创建高级Web测试计划(详解教程)

    1.简介 上一篇中宏哥已经将其的理论知识介绍了一下,这一篇宏哥就带着大家一步一步的把上一篇介绍的理论知识实践一下,然后再说一下如何创建高级web测试计划. 2.网络计划实战 通过上一篇的学习,宏哥将其 ...