1 在创建表的时候指定分区
默认情况下,在创建HBase表的时候会自动创建一个region分区,当导入数据的时候,所有的HBase客户端都向这一个region写数据,直到这个region足够大了才进行切分。一种可以加快批量写入速度的方法是通过预先创建一些空的regions,这样当数据写入HBase时,会按照region分区情况,在集群内做数据的负载均衡。
- 实现方式是使用admin对象的切分策略
byte[] startKey = ...; // your lowest key
byte[] endKey = ...; // your highest key
int numberOfRegions = ...; // number of regions to create
admin.createTable(table, startKey, endKey, numberOfRegions);
- 用户自定义切片
byte[][] splits = ...; // create your own splits
/*
byte[][] splits = new byte[][] { Bytes.toBytes("100"),
Bytes.toBytes("200"), Bytes.toBytes("400"),
Bytes.toBytes("500") };
*/
admin.createTable(table, splits);
2 Rowkey设计
HBase中rowkey用来检索表中的记录,支持以下三种方式:
1. 通过单个rowkey访问:即按照某个rowkey键值进行get操作;
2. 通过rowkey的range进行scan:即通过设置startRowKey和endRowKey,在这个范围内进行扫描;
3. 全表扫描:即直接扫描整张表中所有行记录
在HBase
中,rowkey
可以是任意字符串,最大长度64KB
,实际应用中一般为10~100
字节,存为byte[]
字节数组,一般设计成定长的。
rowkey
是按照字典序存储,因此,设计rowkey
时,要充分利用这个排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。
Rowkey设计原则:
1. 越短越好,提高效率
* 数据的持久化文件HFile
中是按照KeyValue
存储的,如果rowkey
过长,比如超过100
字节,1000
万行数据,单单是存储rowkey
的数据就要占用10
亿个字节,将近1G
数据,这样会影响HFile
的存储效率。
* HBase
中包含缓存机制,每次会将查询的结果暂时缓存到HBase
的内存中,如果rowkey
字段过长,内存的利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。
2. 散列原则——实现负载均衡
如果rowkey
是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey
的高位作为散列字段,由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个regionserver
实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息将产生所有新数据都在一个 regionServer
上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别regionServer
,降低查询效率。
* 加盐:添加随机值
* hash:采用md5散列算法取前4位做前缀
* 反转:将手机号反转(尽量将具有随机性的字符串放在前面,这样便于Hash
)
3. 唯一原则–字典序排序存储
必须在设计上保证其唯一性,rowkey
是按照字典顺序排序存储的,因此,设计rowkey
的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。
3 列族的设计
不要在一张表里定义太多的column family
。目前Hbase
并不能很好的处理超过2~3
个column family
的表。因为某个column family
在flush
的时候,它邻近的column family
也会因关联效应被触发flush
,最终导致系统产生更多的I/O
。
原因:
1. 当开始向hbase
中插入数据的时候,数据会首先写入到memstore
,而memstore
是一个内存结构,每个列族对应一个memstore
,当包含更多的列族的时候,会导致存在多个memstore
,每一个memstore
在flush
的时候会对应一个hfile
的文件,因此会产生很多的hfile
文件,更加严重的是,flush
操作时region
级别,当region
中的某个memstore
被flush
的时候,同一个region
的其他memstore
也会进行flush
操作,当某一张表拥有很多列族的时候,且列族之间的数据分布不均匀的时候,会产生更多的磁盘文件;
2. 当hbase
表的某个region
过大,会被拆分成两个,如果我们有多个列族,且这些列族之间的数据量相差悬殊的时候,region
的split
操作会导致原本数据量小的文件被进一步的拆分,而产生更多的小文件;
3. 与Flush
操作一样,目前HBase
的Compaction
操作也是Region
级别的,过多的列族也会产生不必要的I/O
;
4. HDFS
其实对一个目录下的文件数有限制的(dfs.namenode.fs-limits.max-directory-items
)。如果我们有N
个列族,M
个Region
,那么我们持久化到HDFS
至少会产生N*M
个文件,而每个列族对应底层的HFile
文件往往不止一个,我们假设为K
个,那么最终表在HDFS
目录下的文件数将是N*M*K
,这可能会操作HDFS
的限制。
4 in memory
hbase
在LRU
缓存基础之上采用了分层设计,整个blockcache
分成了三个部分,分别是single
、multi
和inMemory
。
三者区别如下:
single
:如果一个block
第一次被访问,放在该优先队列中;
multi
:如果一个block
被多次访问,则从single
队列转移到multi
队列
inMemory
:优先级最高,常驻cache
,因此一般只有hbase
系统的元数据,如meta
表之类的才会放到inMemory
队列中。
5 保存版本数
创建表的时候,可以通过ColumnFamilyDescriptorBuilder.setMaxVersions(int maxVersions)
设置表中数据的最大版本,如果只需要保存最新版本的数据,那么可以设置setMaxVersions(1)
,保留更多的版本信息会占用更多的存储空间。
6 设置最大保存时间
创建表的时候,可以通过ColumnFamilyDescriptorBuilder.setTimeToLive(int timeToLive)
设置表中数据的存储生命期,过期数据将自动被删除,例如如果只需要存储最近两天的数据,那么可以设置setTimeToLive(2 * 24 * 60 * 60)
。
7 合并操作
在HBase
中,数据在更新时首先写入WAL
日志(HLog
)和内存(MemStore
)中,MemStore
中的数据是排序的,当MemStore
累计到一定阈值(64MB
)时,就会创建一个新的MemStore
,并且将老的MemStore
添加到flush
队列,由单独的线程flush
到磁盘上,成为一个StoreFile
。于此同时, 系统会在zookeeper
中记录一个redo point
,表示这个时刻之前的变更已经持久化了。
StoreFile
是只读的,一旦创建后就不可以再修改。因此Hbase
的更新其实是不断追加的操作。当一个Store
中的StoreFile
达到一定的阈值后,就会进行一次合并,将对同一个key
的修改合并到一起,形成一个大的StoreFile
,当StoreFile
的大小达到一定阈值后,又会对StoreFile
进行分割,等分为两个StoreFile
。
由于对表的更新是不断追加的,处理读请求时,需要访问Store
中全部的StoreFile
和MemStore
,将它们按照row key
进行合并,由于StoreFile
和MemStore
都是经过排序的,并且StoreFile
带有内存中索引,通常合并过程还是比较快的。
实际应用中,可以考虑必要时手动进行major compact
,将同一个row key
的修改进行合并形成一个大的StoreFile
。同时,可以将StoreFile
设置大些,减少split
的发生。
hbase
为了防止小文件(被刷到磁盘的menstore
)过多,以保证保证查询效率,hbase
需要在必要的时候将这些小的store file
合并成相对较大的store file
,这个过程就称之为compaction
。在hbase
中,主要存在两种类型的compaction
:minor compaction
和major compaction
。
1. minor compaction
: 的是较小、很少文件的合并。minor compaction
的运行机制要复杂一些,它由一下几个参数共同决定:
hbase.hstore.compaction.min
: 默认值为3
,表示至少需要三个满足条件的store file
时,minor compaction
才会启动
hbase.hstore.compaction.max
: 默认值为10
,表示一次minor compaction
中最多选取10
个store file
hbase.hstore.compaction.min.size
: 表示文件大小小于该值的store file
一定会加入到minor compaction
的store file
中
hbase.hstore.compaction.max.size
: 表示文件大小大于该值的store file
一定不会被添加到minor compaction
hbase.hstore.compaction.ratio
: 将StoreFile
按照文件年龄排序,minor compaction
总是从older store file
开始选择,如果该文件的size
小于后面hbase.hstore.compaction.max
个store file size
之和乘以ratio
的值,那么该store file
将加入到minor compaction
中。如果满足minor compaction
条件的文件数量大于hbase.hstore.compaction.min
,才会启动。
2. major compaction
的功能是将所有的store file
合并成一个,触发major compaction
的可能条件有:
major_compact
命令
majorCompact() API
region server
运行
hbase.hregion.majorcompaction
默认为24
小时
hbase.hregion.majorcompaction.jetter
默认值为0.2
为防止region server
在同一时间进行major compaction
。
hbase.hregion.majorcompaction.jetter
参数的作用是:对参数hbase.hregion.majorcompaction
规定的值起到浮动的作用,假如两个参数都为默认值24
和0.2
,那么major compact
最终使用的数值为19.2~28.8
这个范围。