HBase表的设计(二)之行健的设计

HBase行健的设计

  在设计HBase表的时候,行健是唯一重要的事情。应该基于预期的访问模式来为行健进行建模

  行健决定了访问HBase表时可以得到的性能。这个结论根植于两个事实:

  1、region基于行健为一个区间的行提供服务,并且负责区间内的每一行。

  2、HFile在硬盘上存储有序的行。

  当region刷写留在内存中的行时生成了HFile,此时这些行已经经过排序了,也会有序的刷写到硬盘上。HBase表的有序特性和底层的存储格式也可以让我们根据如何设计行健以及吧什么放入列限定符来推理性能表现。

  对于关系型数据库来说,我们可以在多个列上建立索引,但是HBase只能在键上建立索引,访问的唯一办法就是使用行健。如果不知道要访问的行健,则就需要扫描若干行,即使不是整个表。对于行健的设计,可以针对不同的访问模式进行优化。

IO考虑

  HBase表的有序特性虽然是能够让我们在最短的时间内扫描一小组行,但是当我们往表中写一堆时间序列的数据时,同样的有序特性会带来负面的影响(比如说热点问题)。

  例如,在使用时间戳做行健的时候,因为对于时间戳来说,它是天然单调递增的,所以说在写入的时候,总是写入到负责这个时间戳范围的一个region上。这样的话不仅使整个集群受限于单个region能够处理的吞吐量,而且还会承担由于单个机器过载而同时集群里其他机器限制的风险。所以针对特定的访问模式来做指定的优化。

为写优化

  当往HBase表写入大量的数据的时候,我们总是希望能够在RegionServer上分散负载来进行优化。要实现这种方式,并不难,只需要将写入数据的行健进行稍作处理,避免使用连续的行健(例如使用时间戳,在写入的时候会写入到单个的region上,造成热点问题),但是这样的话,我们可能就会在读模式下做出点牺牲,因为对rowkey做了处理,所以数据都分布在不同的region上,所以如果需要使用scan扫描一组有序的数据的时候,可能就不是那么理想了。

  但是,在很多场景下,我们并不需要基于单个时间戳访问数据,可能我们需要运行一个作业在一个"条件区间上"来做聚合计算,如果说对时间延迟不是特敏感的,可以考虑跨多个region并行扫描来完成任务,例如下面这样的代码:

val hisData = isContainsHisData.mapPartitions(iter => {
      val hbaseConf = HBaseConfiguration.create
      val con = ConnectionFactory.createConnection(hbaseConf)
      val tn = TableName.valueOf(BASIC_DATA_TABLE_NAME)
      val table = con.getTable(tn)

      val scan = new Scan()
      scan.setCaching(500)

      iter.map(x => {
        conditions1 = (mD5Util.md5Encode(x._1._1 + x._1._2, 16) + "#" + x._2).replace("-", "")
        conditions2 = (mD5Util.md5Encode(x._1._1 + x._1._2, 16) + "#" + String.valueOf(LocalDate.parse(x._2).plusDays(-1))).replace("-", "")
        scan.setStartRow(Bytes.toBytes(conditions2 + "#0"))
        scan.setStopRow(Bytes.toBytes(conditions1 + "#9~"))
        val tmpR = table.getScanner(scan).iterator()
        val resArr = new ArrayBuffer[Result]()
        while (tmpR.hasNext) {
          resArr += tmpR.next()
        }
        resArr
      }).flatMap(x => x)
    })

  但是具体如何对行间进行处理,才能让数据分散到不同的region上呢?有如下几项可以考虑:

  • 散列

   如果我们在行健中不假如时间戳信息的话,那么使用原始拼接数据的散列值作为行健是一种解决方案,如上面代码所示,我一般会对我拼接的rowkey做一个16位的md5(所谓的16位MD5值只是取其32位md5值的8-24位值)。这样的话,每当我们访问这个散列值为行健的数据的时候,就需要精确的知道拼接的rowkey。

  但是对于时间序列而言的话,一般不会这样处理,你也不大可能能够记得精确的时间戳。

  对于计算散列值的方法,常用的有MD5、SHA-1或其他提供的随机分布的散列算法函数

碰撞
    散列算法有一个非零碰撞概率。有些算法比其他算法高,当用于大型数据集是需要小心,要尽量使用低碰撞概率的散列算法。例如,在这方面的话,SHA-1要由于MD5,某些情况下,SHA-1可能是个更好的选择,即使性能上有些许。
  • slating

   在思考行健的构成的时候,salting是另一种技巧。

   假设你在读取的时候之后时间范围,但是不想做全表扫描(一般情况下,我们都不会对HBase表做全表扫描,除非不得已的情况下,否则千万别),对时间戳做散列运算,然后把散列值作为行健的做法需要全表扫描,这是非常低效的。尤其是你有办法限制扫描范围的时候。使用散列值作为行健在这里不是办法,但是你可以在时间戳前面加上一个随机前缀。

  例如,可以先计算时间戳的散列码然后用RegionServer的数量取模来生成随机salt数:

  int salt = new Integer(md5(new Long(timestamp))).shorValue() % <number of region server>

  取得salt之后,加到时间戳的前面生成行健,像这样:

   byte[] rowkey = Bytest.toBytest(salt+"|"+timestamp)

  现在行健如下所示:

  0|timestamp1

  0|timestamp5

  0|timestamp6

  1|timestamp2

  1|timestamp9

  2|timestamp4

  2|timestamp8

  此时,这些行会基于键的第一部分分部在不同的region上,也就是说salt相同的会分部在同一个region上,除非发生region的拆分。

  不过先在,如果要读取的话,那就需要将扫描分散在不同的region上来查找相应的行。因为它们不在存储在一起,所以,此时一个短的扫描不能够解决问题。故这是一种权衡

为读优化

  对于HBase的读取,要充分利用行健的特性,在获取数据之前务必想清楚,获取数据的方式,常用的获取数据的方式有三种:scan、get、批量get

  • scan:在开发过程中应该尽量避免全表scan,使用的过程中务必设置startRow和stopRow。

    行健:md5(aaa)#20190102#001

       md5(aaa)#20190103#101

      md5(aaa)#20190104#221

       md5(aaa)#20190105#431

       ...

    如上rowkey,如果需要获取aaa对应的20190103之后的所有数据,正确的做法应该是:

    scan.setStartRow(Bytes.toBytes(md5(aaa)+"#20190103"+"#0"))

    scan.setStopRow(Bytes.toBytes(md5(aaa)+"#9~"))

    table.getScanner(scan)

    错误的做法通常都是,不对scan进行设置startRow与stopRpw,全表扫描,然后在使用过滤器,这种做法是非常的低效的。

  • get:如果说我们在获取数据之前明确的知道对应的rowkey,则可以使用get的方式,这种方式简单,高效,但是这种方式对于获取单条记录简单,如果我们要获取1000个rowkey的数据的,难道我们要get1000次么?当然不是,这个时候就需要用到下面的方式。
  • List<get>:批量get。该种方式在明确rowkey并且在需要获取的rowkey较多的时候非常适用。

    List<Get> gets = new ArrayList<Get>(20);
    Get get = null;
    for (String rowkey : rowkeyMaps.keySet()) {
                get = new Get(Bytes.toBytes(rowkey);
                gets.add(get);
    }
    Result[] reslut = table.get(gets);

基数和行健结构

  行健结构至关重要。有效的行健设计不仅要考虑把什么放入到行健中,而且要考虑它们在行健中的位置。

  假设现在系统有种三个用户,分别是:TheRealMT、TheFakeMT和Olivia,现在考虑时间区间1-10之内倒序时间戳。如果说把用户ID放在第一部分,行健如下所示(按照它们在HBase表里的存储顺序):

  Olivia1

  Olivia2

  Olivia5

  Olivia7

  Olivia9

  TheFakeMT2

  TheFakeMT3

  TheFakeMT4

  TheFakeMT5

  TheFakeMT6

  TheRealMT1

  TheRealMT2

  TheRealMT5

  TheRealMT8

但是,如果调换键的顺序,把倒序时间戳放在第一部分,行健排序变为:

  1Olivia

  2TheFakeMT

  2Olivia

  2TheRealMT

  3TheFakeMT

  4TheFakeMT

  5Olivia

  5TheFakeMT

  5TheRealMT

  6TheFakeMT

  7Olivia

  8TheRealMT

  9Olivia

  因为不能在指定用户ID作为扫描键的起始键,因此要想获取指定用户的最新数据,都需要扫描整个时间范围。

  所以说,在设计行健的时候,行健里的位置和选择放入什么信息同等重要

原文地址:https://www.cnblogs.com/Gxiaobai/p/12194624.html

时间: 2024-07-28 15:12:52

HBase表的设计(二)之行健的设计的相关文章

游戏UI框架设计(二) : 最简版本设计

最简版本设计 --最简版本设计 为降低难度决定先讲解一个最简版本,阐述UI框架的核心设计理念.这里先定义三个核心功能: 1:UI窗体的自动加载功能. 2:缓存UI窗体. 3:窗体生命周期(状态)管理. UI框架设计主要目的,就是尽可能的完成一些与具体游戏功能逻辑无关的一些底层事务性的功能实现.这些功能最好是自动或者是半自动的实现,无须客户程序(调用框架的程序)过多处理与关心. 对于以上功能,笔者定义了UI框架的相关四个核心类: BaseUIForms    基础UI窗体脚本(父类,其他窗体都继承

hbase 表的设计与其它大数据框架的集成

一:hbase 表的设计管理 二:hbase hive 集成 三:sqoop 与hbase 的集成 四:hbase 与hue 集成 五:hbase 表的修复 一:hbase 表的设计管理 1.1 hbase 的shell 命令 1.1.1 创建一个命名空间 在新版本的hbase 中 表是存储在命名空间当中,默认的命名空间是default 创建一个命名空间: create_namespace 'ns2' 查看有多少个命名空间: list_namespace 在命名空间中建立表: create 'n

HBase笔记整理(二)

[TOC] HBase笔记整理(二) 逻辑结构 RowKey第一位 ColumnFamily ColumnQuiauer value(TimeStamps) Cell 物理结构 HMaster ----->NameNode 管理节点,用于管理HBase中的Table和Region的结构操作,比如用户增.删.修改表的操作. 在HBase集群中,可以启动多个HMaster,但是只能有一个HMaster属于Active的状态,通过ZooKeeper和其它standby状态的HMaster进程完成,一个

HBase表的设计(一)

HBase表模式的设计 对于HBase表,在设计表结构之前,我们需要先考虑的几个问题: 这个表应该有多少个列族? 列族使用的是什么数据? 每个列族应该有多少列? 列名应该是什么?尽管列名不必在建表的时候定义,但是后期读写数据时是需要知道的. 单元存放什么数据? 每个单元存储多少个时间版本? 行健结构是什么?应该包含什么信息? 模式影响到表结构和如何读写表,所以说把这些放到宽泛的模式设计中变得尤为重要. 一.HBase的存储方式 HBase底层物理存储是基于HDFS,在HDFS上是以HFile的形

hbase表设计优化原则 ***** 生产环境中使用小结

2019/2/28 星期四 hbase表设计优化原则 https://www.cnblogs.com/qingyunzong/p/8696962.html表设计1.列簇设计 追求的原则是:在合理范围内能尽量少的减少列簇就尽量减少列簇. 最优设计是:将所有相关性很强的 key-value 都放在同一个列簇下,这样既能做到查询效率 最高,也能保持尽可能少的访问不同的磁盘文件. 以用户信息为例,可以将必须的基本信息存放在一个列族,而一些附加的额外信息可以放在 另一列族.2.RowKey 设计 HBas

数据分页处理系列之二:HBase表数据分页处理

  HBase是Hadoop大数据生态技术圈中的一项关键技术,是一种用于分布式存储大数据的列式数据库,关于HBase更加详细的介绍和技术细节,朋友们可以在网络上进行搜寻,笔者本人在接下来的日子里也会写一个HBase方面的技术专题,有兴趣的朋友们可以稍微的期待一下.不过本章节的重点是介绍下HBase表数据的分页处理,其他的就不多说了. 首先说一下表数据分页中不可回避的一个指标:总记录数.在关系数据库中很容易统计出记录总数,但在HBase中,这却是一个大难题,至少在目前,朋友们根本不要奢望能够通过类

HBase表设计

1.Column Family 由于Hbase是一个面向列族的存储器,调优和存储都是在列族这个层次上进行的,最好使列族成员都有相同的"访问模式(access pattern)"和大小特征. 在一张表里不要定义太多的column family.目前Hbase并不能很好的处理超过2~3个column family的表.因为某个column family在flush的时候,它邻近的column family也会因关联效应被触发flush,最终导致系统产生更多的I/O. 2.Row Key 1

Hbase表的设计

1. 表的设计 1.1 Pre-Creating Regions 默认情况下,在创建HBase表的时候会自动创建一个region分区,当导入数据的时候,所有的HBase客户端都向这一个region写数据,直到这个region足够大了才进行切分.一种可以加快批量写入速度的方法是通过预先创建一些空的regions,这样当数据写入HBase时,会按照region分区情况,在集群内做数据的负载均衡. 下面是一个例子: public static boolean createTable(HBaseAdmi

hbase 学习(十二)集群间备份原理

集群建备份,它是master/slaves结构式的备份,由master推送,这样更容易跟踪现在备份到哪里了,况且region server是都有自己的WAL 和HLog日志,它就像mysql的主从备份结构一样,只有一个日志来跟踪.一个master集群可以向多个slave集群推送,收到推送的集群会覆盖它本地的edits日志. 这个备份操作是异步的,这意味着,有时候他们的连接可能是断开的,master的变化不会马上反应到slave当中.备份个格式在设计上是和mysql的statement-based