HBase优化
一.表设计
1.预分区【Pre-Creating Regions】
默认情况下,在创建HBase表的时候会自动创建一个region分区,当写入数据时,所有的HBase客户端都向这一个region写数据,直到这个region足够大时才进行切分。因此,为了提高批量写入的效率可以预先创建好多个分区【这个要和rowkey的设计紧密关联】,这样当数据写入HBase时会按照region分区的情况,不同rowkey类型的数据写入对应的分区中,写入数据的速度会大大提高,同时也做到了数据的负载均衡。
2.Row Key【行键】
HBase中的rowkey用来检索表中的记录,支持一下三种方式:
1.通过单个rowkey访问,即按照某个rowkey键值进行get操作。
2.通过rowkey的range进行scan,即通过设置startRowKey和endRowKey在这个范围内进行扫描。
3.全表扫描,及直接扫描整张表中所有行记录。
备注:
1.在HBase中rowkey可以是任意字符串,最大长度64KB,实际应用中一般为10~100bytes,存为byte[]字节数组,一般设计成定长的。
2.rowkey是按照字典顺序存储,因此,可以在设计rowkey时充分利用这一点,将经常一起读取的数据存储到一块,将最近可能被访问的数据放在一块。
3.Column Family【列簇】
不要在一张表里定义太多的column family。目前HBase并不能很好的处理超过2~3个column family的表。因为某个column family在flush的时候,它邻近的column family因关联效应被触发flush,最终导致系统产生更多的I/O。
4.In Memory
创建表时,可以通过HColumnDescriptor.setInMemory(true)将表放到RegionServer缓存中,保证在读取的时候被cache命中。
5.Max Version
创建表时,可以通过HColumnDescriptor.setMaxVersions(int maxVersions)设置数据的最大最大版本。
6.Time To Live
创建表时,可以通过HColumnDescriptor.setTimeToLive(int timeToLive)设置表中数据的生命周期,过期数据将自动被删除,例如如果只需要存储最近一天的数据,那么可以设置为HColumnDescriptor.setTimeToLive(1 * 24 * 60 * 60)。
7.Compact & Split
在HBase中,数据在更新时首先写入WAL(HLog)和内存(MemStore)中,MemStore数据是排序的,当MemStore累计到一定阈值时,就会创建一个新的MemSotee,并且将老的MemStore添加到flush队列,由单独的线程flush到磁盘上,成为一个StoreFile。与此同时系统会在zookeeper中记录一个redo point,表示这个时刻之前的变更已经持久化了(minor compact)。StoreFile是只读的,一旦创建后就不能再修改,因此HBase的更新其实是不断追加的过程,若一个Store中的StoreFile达到一定的阈值后,就会进行一次合并(major compact),将对同一个Key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小达到一定阈值时又会对StoreFile进行切分(split),等分成两个SotreFile。由于对表的更新是不断追加的,处理读请求时,需要访问Store中全部的StoreFile和MemStore,将它们按照row key进行合并,由于StoreFile和MemSrore都是进行过排序的,并且StoreFile带有内存索引,通常合并过程是比较快的。实际应用中,可以考虑必要时手动进行major compact,将同一个row key的修改进行合并形成一个大的StoreFile。同时可以将SoreFile设置大些,减少split的发生。
二.写表
1.创建多个HTable客户端写数据,提高写数据的吞吐量
Configuration conf = HBaseConfiguration.create();
List<HTable> list = new ArrayList<HTable>();
for(int i = 0; i<10; i++){
list.add(new HTable(conf, table_name));
}
2.批量写
调用HTable.put(List<Put>)
3.多线程并发写
在客户端开启多个HTable写线程,每个写线程负责一个HTable对象的flush操作,这样结合定时flush和写buffer(writeBufferSize),可以既保证在数据量少的时候数据可以在较短时间内被flush,同时又保证在数据量大的时候写buffer一满就及时进行flush。
4.参数优化
4.1.Auto Flush
通过调用HTable.setAutoFlush(false)方法可以将HTable写客户端的自动flush关闭,这样可以批量写数据到HBase,而不是一条数据执行一次更新,只用当put写满客户端缓存时才实际向HBase服务端发起写请求,默认情况下auto flush是开启的。
4.2.Write Buffer
通过调用HTable.setWriteBufferSize(wirteBufferSize)方法可以设置HTable客户端的写buffer大小,如果新设置的buffer小于当前写buffer中的数据大小时,buffer将会被flush到服务器端。其中,writeBufferSize的单位是byte字节数,可以根据实际写入数据量的多少来设置该值。
4.3.WAL Flag
在HBase中,客户端向集群中的RegionServer提交数据时(Put/Delete),首先会写WAL(即HLog,一个RegionServer上所有Region共享一个HLog),只有当WAL写成功后,再接着写MemStore,然后客户端被通知提交数据成功。如果写WAL失败,客户端则会被通知提交失败,这样做的好处是可以做到RegionServer宕机后数据恢复。
三.读表
1.多HTable并发读
同上,与多HTable并发写类似
2.参数优化
2.1Scanner Caching
hbase.client.scanner.caching配置项可以设置HBase Scanner一次从服务端抓取的数据条数,默认一次返回一条数据。通过将其设置成一个合理的值,可以减少scan过程中next()的时间开销,代价是scanner需要通过客户端的内存来维持这些被cache的数据。
有三个地方可以进行配置:
1.HBase的conf配置文件
2.HTable.setScannerCaching(int scannerCaching) 【最优选择】
3.Scan.setCaching(int caching)
2.2 Scan Attribute Selection
scan时指定需要的Column Family,这样可以减少网络传输时的数据量,否则默认scan操作返回整行数据。
2.3 Close ResultScanner
通过scan去完数据后,记得要关闭ResultScanner,否则RegionServer可能会出现问题(对应server资源无法释放)。
3.批量读
HTable.get(List<Get>)
4.多线程并发读
同上
5.缓存查询结果
对于频繁查询HBase的应用场景,可以考虑在应用程序中做缓存。当有新的查询请求时,首先在缓存中查找,如果存在则直接返回,不在查询HBase,否则对HBase发起读请求。
6.Blockcache
HBase上Regionserver的内存分为两部分:Memstore【主要用来写数据】,BlockCache【主要用来读数据】。写请求会先写入Memstore,Regionserver会给每个region提供一个Memstore,当Memstore满64M时,会启动flush刷新到磁盘。读请求会先到Memstore中查数据,查不到就导BlockCache中查,再查不到就会到磁盘上读取数据,并把结果写入BlockCache中。
7.使用HTablePool
try{
Configuration conf = HBaseConfiguration.create();
Connection connection = ConnectionFactory.createConnection(conf);
Table table = connection.getTable(TableName.valueOf(Tablename));
}cache(Exception e){
e.printSchema();
}