表的设计

1 Pre-Creating Regions

默认情况下,在创建HBase 表的时候会自动创建一个region 分区,当导入数据的时候,所有的
HBase客户端都向这一个region写数据,直到这个region 足够大了才进行切分,一种可以加快批量写入的速度的方法通过
预先创建一些空的region,这样当数据写入HBase时,会按照region区分情况,在集群内做数据的负载均衡

  • 1.1 Row Key

HBase 中row key用来检索表中的记录,支持一下三种方式:

.通过单个row key 访问:即按照某个row key 键值进行get操作
.通过row key 的range 进行scan:即通过设置startRowKey 和 endRowKey,在这个范围内进行扫描;
.全表扫描:即直接扫描整张表中的所有行记录。

在HBase 中,row key 可以是任意字符串吗,最大大长度64KB,实际应用中一般
10~100bytes, 存为byte[]字节数组,一般设计成定长的。

row key 是按照字典存储,因此,设计 row key时,要充分利用这个排序特点,将经常一起读 取的数据存储到一块,将最近可能会被访问数据放在一块。

举个列子:如果最近写入HBase表中的数据是最可能被访问的,可以考虑将时间戳作为 row key 的一部分,由于是字典排序,所以可以使用 Long.MAX_VALUE-timestamp 作为 row key
这样能保证新写入的数据在读取时可以被快速命中。

  • 1.2 Column Family

不要在一张表里定义太多的column family,目前Hbase并不能很好的
处理超过2~3个 column family 的表,因为某个column family

在flush的时候,它领近的column family 也会因关联效应被

触发flush,最终导致系统产生更过的I/O。

  • 1.3 In Memory

创建表的时候,可以通过HColumnDescriptor.setInMemory(true)
将表放到RegionServer的缓存中,保证读取的时候被cache命中。

  • 1.4 Max Version

创建表的时候,可以通过HColumnDescriptor.setMaxVersions(int maxVersions)设置表中数据的最大版,如果只需要保存最新版本的数据
那么可以设置setMaxVersions(1).

  • 1.5 Time To Live

创建表的时候,可以通过HColumnDescriptor.setTimeToLive(int timeToLive)设置表中的数据的存储生命期。过期数据将自动被删除
例如如果只需要存储最近两天的数据那么可以设置setTimeToLive(2*24*60*60)

  • 1.6 Compact & Split

在HBase中,数据再更新时首先写入WAL日志(Hlog)和内存(MemStore)中,MemStore中的数据是排序的,当MemStore累计到一定阀值时,就会创建一个新的MemStore,
并且将老的MemStore添加到flush队列,由单独的线程flush到磁盘,成为一个StoreFile.于此同时,
系统会在zookeeper中记录一个redo point,表示这个时刻之前的变更已经持久化了(minorcompact).

StoreFile 是只读的,一旦创建后就不可以在做修改,因此Hbase的更新其实是不断追加的操作,当一个store中的
StoreFile达到一定的阀值后,就会进行一次合并(major compact),将对通一个key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小到一定阀值后,
又会对StoreFile进行分割(split),等分为两个StoreFile。

由于对表的更新时不断追加的,处理请求时,需要访问Store中全部StoreFile和MemStore
将他们按照row key 进行合并,由于StoreFile 和 MemStore 都是经过排序的,并且StoreFile 带有内存中索引,通常合并过程还是比较快的。

实际应用中,可以考虑不要时进行手动 major compact,将同一个row key 的修改进行合并形成一个大的StoreFile。同时将StoreFile设置大些,减少split的发生。

2 HTable 参数设置

  • 2.1 Auto Flush

通过调用HTable.setAutoFlus(false)方法可以将HTable 写客户点的自动flush关闭,这样可以批量写入数据到HBase,而不是有一条put就执行一次更新,只有当put填满客户端写的缓存时,才实际向HBase服务端发起请求。默认情况下auto flush 是开启的。

  • 2.2 Write Buffer

通过调用HTable.setWriteBuffer(writeBufferSize)方法可以设置HTable客户端的写Buffer大小,如果新设置的buffer 小于当前 buffer中的数据时,buffer将会被flush到服务器端,其中,writeBufferSize的单位是byte字节数,可以根据实际写入数据量的多少来设置该值.

  • 2.3 WAL Flag

在HBase 中,客户端向集群中的RegionServer提交数据时(Put/Delete操作),
首先会先写WAL (Write Ahead Log) 日志(即HLog,一个RegionServer上的所有Region共享一个HLog),只有当WAL 日志写成功后,再接着写MemStore,然后客户端被通过提交数据成功;如果WAL日志失败,客户端则被通知提交失败。这样做的好处是可以做到RegionServer宕机后的数据恢复。

因此,对于相对不太重要的数据,可以在Put/Delete操作时,通过Put.setWriteToWAL(false)
或者Delete.setWriteToWAL(false)函数,放弃写WAL日志,从而提高数据写的性能。

值得注意的是:谨慎选择关闭WLA日志,因为这样的话,一旦RegionServer宕机,
Put/Delete的数据将会无法根据WAL日志进行恢复。

  • 2.4 批量写

通过调用HTable.put(Put)方法可以将一个指定的row key记录写入HBase,同样HBase提供了另一个方法:
通过调用HTable.put(List)方法可以将指定的row key列表,批量写入多行记录,这样做的好处是批量执行
只有要一次网络I/O开销,这对于数据实时性要求高,网络传送RTT高的情景下可能带来明显的性能提升。

  • 2.5 多线程并发写

在客户端开多个HTable写线程,每个写线程负责一个HTable对象的flush操作,这个结合定时flush 和 写buffer (writeBufferSize),可以既保证在数量小的时候,数据可以在较短时间内被flush(如1秒),同时又保证在数量大的时候,写buffer一满就及时进行flush。

3 HTable 参数设置

  • 3.1 Scanner Caching

hbase.client.scanner.caching 配置项可以设置HBase scanner 一次从
服务器抓取的数据条数,默认情况下一次一条,通过将期设置成一个合理的值,
可以减少 scan 过程中next()的时间开销,代价是scanner需要通过客户端的内存来维持这些被cache的行记录,有三个地方可以进行配置:

1.在HBase的conf配置文件中进行配置;
2.通过调用HTTable.setScannerCaching(int scannerCahing)进行配置;
3.通过调用Scan.setCaching(int caching)进行配置,三者的优先级越来越高。
  • 3.2 Scan Attribute Selection

scan 时指定需要的Column Fanily,可以减少网络传输数据量,负责默认scan操作会返回整行所有Column Family的数据。

  • 3.3 Close ResultScanner

通过scan 取完数据后,记得要关闭ResultScanner,否则RegionServer 可能会传问题(对应的Server资源无法释放).

  • 3.4 批量读

通过调用HTable.get(Get)方法可以根据一个指定的 row key 获取一行记录,
同样HBase提供了另一个方法:通过调用HTable.get(List)方法可以根据一个指定的row key 列表,批量获取多行记录,这样做的好处事批量执行,只需要一次网络I/O开销,这对数据实时性要求高而且网络传输RTT高的情景下可能带来明显的性能提升。

  • 3.5 多线程并发读

在客户端开多个HTable 读线程,每个读线程负责通过HTable对象进行get操作。

  • 3.6 缓存查询结果

对于频发查询HBase的应用场景,可以考虑在应用程序中作缓存,当有新的查询请求是,首先在缓存中查找,如果存在则直接返回,不在查询HBase;否则对HBase发起读求查询,
然后在应用程序中将查询结果缓存起来,至于缓存的替换策略,可以考虑LRU等常用的策略。