client发送写的请求,获取meta region路由信息--------->zk集群;
zk返回meta region的路由信息(HRegionServer1)---------->client
client获取根据rowkey获取在meta表中的region信息----->HRegionServer1
HRegionServer1返回region信息(HRegionServer2)-------------->client
client访问HRegionServer2,发送写入数据请求----------->HRegionServer2
HRegionServer2拿到请求进行region分发------------->region
region内部将数据hbase做写操作时,先记录在本地的wal(Write-Ahead logfile)中,但是不同步到hdfs------->再写入memstore----------------->开始将wal同步到hdfs------->memstore中数据达到一定阈值后,进行数据的刷写生成HFile存入HDFS
是什么
HBase的Write Ahead Log (WAL)提供了一种高并发、持久化的日志保存与回放机制。每一个业务数据的写入操作(PUT / DELETE)执行前,都会记账在WAL中。
为什么?
WAL最重要的作用是灾难恢复,和Mysql的BIN log类似,它记录着所有数据的改动。在正常操作下,不需要WAL,因为数据更改从MemStore移动到StoreFiles。但是,如果在刷新MemStore之前RegionServer崩溃或变得不可用,则WAL确保可以重播对数据所做的更改。如果写入WAL失败,则修改数据的整个操作将失败。
怎么做?
是什么?
就是内存存储,位于内存中,用来保存当前的数据操作,所以当数据保存在WAL中之后,RegsionServer会在内存中存储键值对。
为什么?
HBase 写数据(比如 put、delete)的时候,都是写 WAL(假设 WAL 没有被关闭),然后将数据写到一个称为 MemStore 的内存结构里面的,但是,MemStore 毕竟是内存里面的数据结构,写到这里面的数据最终还是需要flush持久化到磁盘的,生成 HFile。
Region 中所有 MemStore 占用的内存超过相关阈值
当一个 Region 中所有 MemStore 占用的内存(包括 OnHeap + OffHeap)大小超过刷写阈值的时候会触发一次刷写。
hbase.hregion.memstore.flush.size:128M
如果我们的数据增加得很快,达到
==hbase.hregion.memstore.flush.size hbase.hregion.memstore.block.multiplier(4)==的大小,也就是1284=512MB的时候,那么除了触发 MemStore 刷写之外,HBase 还会在刷写的时候同时阻塞所有写入该 Store 的写请求!这时候如果你往对应的 Store 写数据,会出现 RegionTooBusyException 异常。
整个 RegionServer 的 MemStore占用内存总和大于相关阈值
HBase 为 RegionServer 的 MemStore 分配一定的写缓存,写缓存大概占用 RegionServer 整个 JVM 内存使用量的 40%。如果整个 RegionServer 的 MemStore 占用内存总和大于 hbase.regionserver.global.memstore.size.lower.limit (0.95)* hbase.regionserver.global.memstore.size(0.4) * hbase_heapsize 的时候,将会触发 MemStore 的刷写。
WAL数量大于相关阈值
定期自动刷写
这个时间是由 hbase.regionserver.optionalcacheflushinterval参数控制的,默认是 3600000,也就是1小时会进行一次刷写。如果设定为0,则意味着关闭定时自动刷写。
数据更新超过一定阈值
如果 HBase 的某个 Region 更新的很频繁,而且既没有达到自动刷写阀值,也没有达到内存的使用限制,但是内存中的更新数量已经足够多,比如超过 hbase.regionserver.flush.per.changes 参数配置,默认为30000000,那么也是会触发刷写的。
手动触发刷写
可以通过执行相关命令或 API 来触发 MemStore 的刷写操作。
我们常见的 put、delete、append、increment、调用 flush 命令、Region 分裂、Region Merge、bulkLoad HFiles 以及给表做快照操作都会对上面的相关条件做检查,以便判断要不要做刷写操作。
每个HRegionServer中都会有一个HLog对象,HLog是一个实现Write Ahead
Log的类,每次用户操作写入Memstore的同时,也会写一份数据到HLog文件中,HLog文件定期会滚动出新,并删除旧的文件(已持久化到Storefile中的数据),当HRegionServer意外终止后,HMaster会通过Zookeeper感知,HMaster首先处理遗留的HLog文件,将不同region的log数据拆分,分别放在相应region目录下,然后再将失效的region(带有刚刚拆分的log)重新分配,领取到这些region的HRegionServer在Load
Region的过程中,会发现有历史HLog需要处理,因此会Replay
HLog中的数据到Memstore中,然后flush到StoreFile,完成数据恢复。
每个 RegionServer 包含多个 Region,而每个 Region 又对应多个 Store,每一个 Store 对应表中一个列族的存储,且每个 Store 由一个 MemStore 和多个 StoreFile 文件组成。
StoreFile 在底层文件系统中由 HFile 实现,也可以把 Store 看作由一个 MemStore 和多个 HFile 文件组成。MemStore 充当内存写缓存,默认大小 64MB,当 MemStore 超过阈值时,MemStore 中的数据会刷新到一个新的 HFile 文件中来持久化存储。
久而久之,每个 Store 中的 HFile 文件会越来越多,I/O 操作的速度也随之变慢,读写也会延时,导致慢操作。因此,需要对 HFile 文件进行合并,让文件更紧凑,让系统更有效率
是什么?
Major合并针对的是给定 Region 的一个列族的所有 HFile。它将 Store 中的所有 HFile 合并成一个大文件,有时也会对整个表的同一列族的 HFile 进行合并,这是一个耗时和耗费资源的操作,会影响集群性能。
怎么做?
一般情况下都是做 Minor 合并,不少集群是禁止 Major 合并的,只有在集群负载较小时进行手动 Major 合并操作,或者配置 Major 合并周期,默认为 7 天。另外,Major 合并时会清理 Minor 合并中被标记为删除的 HFile。
是什么?
Minor 合并是把多个小 HFile 合并生成一个大的 HFile。
为什么?
怎么做?
执行合并时,HBase 读出已有的多个 HFile 的内容,把记录写入一个新文件中。然后把新文件设置为激活状态,并标记旧文件为删除。
在 Minor 合并中,这些标记为删除的旧文件是没有被移除的,仍然会出现在 HFile 中,只有在进行 Major 合并时才会移除这些旧文件。对需要进行 Minor 合并的文件的选择是触发式的,当达到触发条件才会进行 Minor 合并,而触发条件有很多,例如, 在将 MemStore 的数据刷新到 HFile 时会申请对 Store下符合条件的 HFile 进行合并,或者定期对 Store 内的 HFile 进行合并。
条件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cHNcQyvZ-1615782802472)(…/…/%E5%A4%A7%E6%95%B0%E6%8D%AE%E6%96%87%E6%A1%A3/%E9%A1%B9%E7%9B%AE%E6%80%BB%E7%BB%93%E6%96%87%E6%A1%A3/assets/1614829346400.png)]
在执行 Minor 合并时,系统会根据上述配置参数选择合适的 HFile 进行合并。Minor 合并对 HBase 的性能是有轻微影响的,因此,合并的 HFile 数量是有限的,默认最多为 10 个。
region在数据量大到一定程度的时候,会进行拆分(最开始由一个变成二个)
region中存储的是大量的rowkey数据 ,当region中的数据条数过多的时候,直接影响查询效率.当region过大的时候.hbase会拆分region。
预拆分
是什么?
在建表的时候就定义好了拆分点的 算法,所以叫预拆分
为什么?
预拆分一部分的作用能减少rowkey热点,另外一部分能减轻region切分时导致的服务不可用。
怎么做?
Hex拆分点:根据 HexStringSplit拆分点算法预拆分为10个Region,同时要建立的列族叫 mycf。(使用命令)
自动拆分
是什么?
hbase自己定的默认的拆分策略
为什么?
Region自动切分是HBase能够拥有良好扩张性的最重要因素之一,也必然是所有分布式系统追求无限扩展性的一副良药。
怎么做?
hbase有很多种默认的拆分策略
手动拆分
是什么?
除了预拆分和自动拆分以外,你还可以对运行了一段时间的Region 进行强制地手动拆分(forced splits)。
怎么做?
调用hbase shell的 split方法
为什么?
当一个Region被不断的写数据,达到Region的Split的阀值时(由属性hbase.hregion.max.filesize来决定,默认是10GB),该Region就会被Split成2个新的Region。随着业务数据量的不断增加,Region不断的执行Split,那么Region的个数也会越来越多。如果有很多 Region,它们中 MemStore 也过多,会频繁出现数据从内存被刷新到 HFile 的操作,从而会对用户请求产生较大的影响,可能阻塞该 Region 服务器上的更新操作。过多的 Region 会增加 ZooKeeper 的负担。
怎么做?
hbase提供合并的命令
# 合并相邻的两个Region
hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME'
# 强制合并两个Region
hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', true
但是这种方式存在问题就是只能一次合并2个Region,如果这里有几千个Region需要合并,这种方式是不可取的。
批量合并,写脚本
问题?
如果在合并Region的过程中出现永久RIT怎么办
是什么?
HBASE表中的每个列,都归属于某个列族。列族是表的schema的一部 分(而列不是),必须在使用表之前定义。列名都以列族作为前缀。例如 courses:history,courses:math都属于courses 这个列族。
为什么?
这么做?
在设置列族之前,我们最好想想,有没有必要将不同的列放到不同的列族里面。如果没有必要最好放一个列族。如果真要设置多个列族,但是其中一些列族相对于其他列族数据量相差非常悬殊,比如1000W相比100行,是不是考虑用另外一张表来存储相对小的列族。
hbase支持大量的算法,并且支持列族级别以上的压缩算法,除非有特殊原因,不然我们应该尽量使用压缩,压缩通常会带来较好的性能。通过一些测试,我们推荐使用SNAPPY这种算法来进行我们hbase的压缩。
设计表的时候,有两种设计方式,一种是高表设计,一种是胖表设计。根据HBase的拆分规则,我们的高表设计更容易拆分(使用组合键),不过如果我们设计成胖表,而我们这个胖表里的数据需要经常修改,这样的设计是很合理的。以为HBase保证了行级原子性,如果设计成高表,反而就不合适了,因为不能保证跨行的原子性。
Client访问zookeeper,获取hbase:meta所在RegionServer的节点信息
Client访问hbase:meta所在的RegionServer,获取hbase:meta记录的元数据后先加载到内存中,然后再从内存中根据需要查询的RowKey查询出RowKey所在的Region的相关信息(Region所在RegionServer)
Client访问RowKey所在Region对应的RegionServer,发起数据读取请求
先从MemStore找数据,如果没有,再到BlockCache里面读;
BlockCache还没有,再到StoreFile上读(为了读取的效率);
找到数据之后会先缓存到blockcache中,再将结果返回;
blockcache逐渐满了之后,会采用LRU的淘汰策略。
HBase读取数据时,首先到memestore上读数据,找不到再到blockcahce上找数据,再查不到则到磁盘查找,并把读入的数据同时放入blockcache。
BlockCache 是 RegionServer 级别的,一个 RegionServer 只有一个 BlockCache,在RegionServer启动的时候完成Block Cache的初始化工作。
BlockCache: HBase会将一次文件查找的Block块缓存到Cache中,以便后续同一请求或者相邻数据查找请求,可以直接从内存中获取,避免昂贵的IO操作。
HBase提供了两种不同的BlockCache实现,用于缓存从HDFS读出的数据。这两种分别为:
当blockcache达到heapsize * hfile.block.cache.size * 0.85时,会启用淘汰机制。
LruBlockCache是最初始的实现,并且全部存在Java堆内存中。BucketCache是另一个选择,主要用于将block cache的数据存在off-heap(堆外内存),不过BlockCache也可以作为一种文件备份式的缓存。
当开启了BucketCache后,便启用了两级缓存的系统。以前我们会用“L1”和“L2”来描述这两个等级,但是现在这个术语已经在hbase-2.0.0后被弃用了。现在“L1” cache 直接指的是LruBlockCache,“L2”指的是一个off-heap的BucketCache。(hbase-2.0.02之后)当BucketCache启用后,所有数据块(DATA block)会被存在BucketCache 层,而meta 数据块(INDEX 以及BLOOM块)被存在on-heap的LruBlockCache中。管理这两层缓存,以及指示数据块如何在它们之间移动的策略,由CombinedBlockCache完成。
LRUBlockCache是默认的BlockCache实现方案。Block数据块都存储在 JVM heap内,由JVM进行垃圾回收管理。
其使用一个ConcurrentHashMap管理BlockKey到Block的映射关系,
缓存Block只需要将BlockKey和对应的Block放入该HashMap中,查询缓存就根据BlockKey从HashMap中获取即可。
同时该方案采用严格的LRU淘汰算法,当Block Cache总量达到一定阈值之后就会启动淘汰机制,最近最少使用的Block会被置换出来。
在具体的实现细节方面,需要关注三点:
缓存分层策略
HBase在LRU缓存基础上,采用了缓存分层设计,将整个BlockCache分为三个部分:Single、Mutile和In-Memory。
将内存从逻辑上分为了三块, 分别占到整个BlockCache大小的25%、50%、25%。
HBase系统元数据存放在InMemory区,因此设置数据属性InMemory = true需要非常谨慎,
确保此列族数据量很小且访问频繁,否则有可能会将hbase.meta元数据挤出内存,严重影响所有业务性能。
LRU淘汰算法实现
系统在每次cache block时将BlockKey和Block放入HashMap后都会检查BlockCache总量是否达到阈值,如果达到阈值,就会唤醒淘汰线程对Map中的Block进行淘汰。
系统设置三个MinMaxPriorityQueue队列,分别对应上述三个分层,每个队列中的元素按照最近最少被使用排列,系统会优先poll出最近最少使用的元素,将其对应的内存释放。
可见,三个分层中的Block会分别执行LRU淘汰算法进行淘汰。
LRU方案优缺点
LRU方案使用JVM提供的HashMap管理缓存,简单有效。
但随着数据从single-access区晋升到mutil-access区,基本就伴随着对应的内存对象从young区到old区 ,
晋升到old区的Block被淘汰后会变为内存垃圾,最终由CMS回收掉(Conccurent Mark Sweep,一种标记清除算法),
然而这种算法会带来大量的内存碎片,碎片空间一直累计就会产生臭名昭著的Full GC。
尤其在大内存条件下,一次Full GC很可能会持续较长时间,甚至达到分钟级别。
大家知道Full GC是会将整个进程暂停的(称为stop-the-wold暂停),
因此长时间Full GC必然会极大影响业务的正常读写请求。BucketCache方案才会横空出世。
get方法:按指定rowkey获取唯一一条记录(点查)
scan方法:按指定条件获取一批记录(范围查)
加载到内存的机制:
优化:
见下面;
HBase中数据仅仅独立地存在于Memstore和StoreFile中,Blockcache中的数据只是StoreFile中的部分数据(热点数据),即所有存在于Blockcache的数据必然存在于StoreFile中。因此MemstoreScanner和StoreFileScanner就可以覆盖到所有数据。实际读取时StoreFileScanner通过索引定位到待查找key所在的block之后,首先检查该block是否存在于Blockcache中,如果存在直接取出,如果不存在再到对应的StoreFile中读取。(常说HBase数据读取要读Memstore、HFile和Blockcache,为什么上面Scanner只有StoreFileScanner和MemstoreScanner两种?没有BlockcacheScanner?)
不需要更新Blockcache中对应的kv,而且不会读到脏数据。数据写入Memstore落盘会形成新的文件,和Blockcache里面的数据是相互独立的,以多版本的方式存在。
比如我们要处理大量行(特别是作为mapreduce的输入源),其中用到scan的时候我们有Scan.addFamily();的方法,这个时候我们如果只是需要到这几个列族中的几个列,那么我们一定要精确,因为过多的列会导致效率的损失。
追求的原则是:在合理范围内能尽量少的减少列簇就尽量减少列簇。
最优设计是:将所有相关性很强的 key-value 都放在同一个列簇下,这样既能做到查询效率 最高,也能保持尽可能少的访问不同的磁盘文件。
以用户信息为例,可以将必须的基本信息存放在一个列族,而一些附加的额外信息可以放在 另一列族。
HBase 中,表会被划分为 1…n 个 Region,被托管在 RegionServer 中。Region 二个重要的 属性:StartKey 与 EndKey 表示这个 Region 维护的 rowKey 范围,当我们要读/写数据时,如 果 rowKey 落在某个 start-end key 范围内,那么就会定位到目标 region 并且读/写到相关的数 据
长度原则
Rowkey 是一个二进制码流,Rowkey 的长度被很多开发者建议说设计在 10~100 个字节,不 过建议是越短越好,不要超过 16 个字节。
原因如下:
唯一原则
必须在设计上保证其唯一性。rowkey 是按照字典顺序排序存储的,因此,设计 rowkey 的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问 的数据放到一块。
散列原则
如果 Rowkey 是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将 Rowkey 的高位作为散列字段,由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个 Regionserver 实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息将产生所有 新数据都在一个 RegionServer 上堆积的热点现象,这样在做数据检索的时候负载将会集中 在个别 RegionServer,降低查询效率。
取反
第三种防止热点的方法是反转固定长度或者数字格式的 rowkey。这样可以使得 rowkey 中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机 rowkey,但是牺 牲了 rowkey 的有序性。
反转 rowkey 的例子以手机号为 rowkey,可以将手机号反转后的字符串作为 rowkey,这 样的就避免了以手机号那样比较固定开头导致热点问题
加盐
这里所说的加盐不是密码学中的加盐,而是在 rowkey 的前面增加随机数,具体就是给 rowkey 分配一个随机前缀以使得它和之前的 rowkey 的开头不同。分配的前缀种类数量应该 和你想使用数据分散到不同的 region 的数量一致。加盐之后的 rowkey 就会根据随机生成的 前缀分散到各个 region 上,以避免热点。
hash
哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是 可以预测的。使用确定的哈希可以让客户端重构完整的 rowkey,可以使用 get 操作准确获取 某一个行数据
时间戳反转
一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为 rowkey 的一部分对这个问题十分有用,可以用 Long.Max_Value - timestamp 追加到 key 的末尾,例 如 [key][reverse_timestamp] , [key] 的最新值可以通过 scan [key]获得[key]的第一条记录,因 为 HBase 中 rowkey 是有序的,第一条记录是最后录入的数据。比如需要保存一个用户的操 作记录,按照操作时间倒序排序,在设计 rowkey 的时候,可以这样设计 [userId 反转][Long.Max_Value - timestamp],在查询用户的所有操作记录数据的时候,直接指 定 反 转 后 的 userId , startRow 是 [userId 反 转 ][000000000000],stopRow 是 [userId 反 转][Long.Max_Value - timestamp]
如果需要查询某段时间的操作记录,startRow 是[user 反转][Long.Max_Value - 起始时间], stopRow 是[userId 反转][Long.Max_Value - 结束时间]
HBase的一级索引就是rowkey,我们只能通过rowkey进行检索。如果我们相对hbase里面列族的列列进行一些组合查询,就需要采用HBase的二级索引方案来进行多条件的查询。
对于 HBase 而言,如果想精确定位到某行记录,唯一的办法是通过 rowkey 来查询,如果不通过 rowkey 来查找数据,就必须逐行地比较每一列的值,即全表扫瞄。
对于数据量较大的表,全表扫描的代价是不可接受的。但是,在很多情况下,我们又不得不需要从多个维度来查询数据。例如,在定位某个人的时候,可以通过姓名、身份证号、学籍号等不同的维度来查询,可要想把这么多维度的数据都放到 rowkey 中几乎不可能(业务的灵活性不允许,对 rowkey 长度的要求也不允许)。所以需要 secondary index(二级索引)来完成这件事。secondary index 的原理很简单,但是如果自己来维护二级索引的话则会麻烦一些。现在,Phoenix 已经提供了对 HBase secondary index 的支持。
二级索引分为全局索引和本地索引。
修改配置文件
如果要启用 Phoenix 的二级索引功能,需要修改 HBase 的配置文件 hbase-site.xml,在 hbase 集群的 conf/hbase-site.xml 文件中添加以下内容。
配置修改完成之后需要重启集群。
使用整合MapReduce的方式创建hbase索引。主要的流程如下:
扫描缓存:HBase在扫描数据时,使用Scanner表扫描器。
块缓存:读取一个数据块到内存缓存中
扫描缓存:hbase.client.scanner.caching配置项可以设置HBase scanner一次从服务器抓取的数据条数,默认情况下一次一行。通过将其设置成一个合理的值,可以减少scan过程中next()的时间开销,代价是scanner需要通过客户机的内存来维持这些被cache的行的记录。
块缓存:首先我们的块缓存是通过Scan.setCacheBlocks();启动的,那么被频繁访问的行,我们应该使用缓存块,但是MapReduce作业使用扫描大量的行,我们就不该使用这个了。
布隆过滤器是一种多哈希函数映射的快速查找算法(存储结构),可以实现用很小的空间和运算代价,来实现海量数据的存在与否的记录(黑白名单判断)。特点是高效的插入和查询,可以判断出一定不存在和可能存在,相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的可能存在结果是概率性的,而不是确切的。
判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较来确定。链表、平衡二叉树、散列表,或者是把元素放到数组或链表里,都是这种思路。以上三种结构的检索时间复杂度分别为O(n), O(logn), O(n/k),O(n),O(n)。而布隆过滤器(Bloom Filter)也是用于检索一个元素是否在一个集合中,它的空间复杂度是固定的常数O(m),而检索时间复杂度是固定的常数O(k)。相比而言,有1%误报率和最优值k的布隆过滤器,每个元素只需要9.6个比特位–无论元素的大小。这种优势一方面来自于继承自数组的紧凑性,另外一方面来自于它的概率性质。1%的误报率通过每个元素增加大约4.8比特,就可以降低10倍。
布隆过滤器是hbase中的高级功能,它能够减少特定访问模式(get/scan)下的查询时间。不过由于这种模式增加了内存和存储的负担,所以被默认为关闭状态。
当我们随机读get数据时,如果采用hbase的块索引机制,hbase会加载很多块文件。
采用布隆过滤器后,它能够准确判断该HFile的所有数据块中是否含有我们查询的数据,从而大大减少不必要的块加载,增加吞吐,降低内存消耗,提高性能
在读取数据时,hbase会首先在布隆过滤器中查询,根据布隆过滤器的结果,再在MemStore中查询,最后再在对应的HFile中查询。
高可用: 在 HBase 中 Hmaster 负责监控 RegionServer 的生命周期,均衡 RegionServer 的负载,如果 Hmaster 挂掉了,那么整个 HBase 集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以 HBase 支持对 Hmaster 的高可用配置。
HDFS调优
Linux优化
ZK优化
优化 Zookeeper 会话超时时间:hbase-site.xml中zookeeper.session.timeout
多Table并发写:创建多个HTable客户端用于写操作,提高写数据的吞吐量
HTable参数优化:
Auto Flush:通过调用HTable.setAutoFlush(false)方法可以将HTable写客户端的自动flush关闭,这样可以批量写入数据到HBase,而不是有一条put就执行一次更新,只有当put填满客户端写缓存时,才实际向HBase服务端发起写请求。默认情况下auto flush是开启的。
Write Buffer:通过调用HTable.setWriteBufferSize(writeBufferSize)方法可以设置HTable客户端的写buffer大小,如果新设置的buffer小于当前写buffer中的数据时,buffer将会被flush到服务端。其中,writeBufferSize的单位是byte字节数,可以根据实际写入数据量的多少来设置该值。
Wal Flag:在HBae中,客户端向集群中的RegionServer提交数据时(Put/Delete操作),首先会先写WAL(Write Ahead Log)日志(即HLog,一个RegionServer上的所有Region共享一个HLog),只有当WAL日志写成功后,再接着写MemStore,然后客户端被通知提交数据成功;如果写WAL日志失败,客户端则被通知提交失败。这样做的好处是可以做到RegionServer宕机后的数据恢复。
因此,对于相对不太重要的数据,可以在Put/Delete操作时,通过调用Put.setWriteToWAL(false)或Delete.setWriteToWAL(false)函数,放弃写WAL日志,从而提高数据写入的性能。
值得注意的是:谨慎选择关闭WAL日志,因为这样的话,一旦RegionServer宕机,Put/Delete的数据将会无法根据WAL日志进行恢复。
批量写:通过调用HTable.put(Put)方法可以将一个指定的row key记录写入HBase,同样HBase提供了另一个方法:通过调用HTable.put(List)方法可以将指定的row key列表,批量写入多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高,网络传输RTT高的情景下可能带来明显的性能提升。
多线程并发:在客户端开启多个HTable写线程,每个写线程负责一个HTable对象的flush操作,这样结合定时flush和写buffer(writeBufferSize),可以既保证在数据量小的时候,数据可以在较短时间内被flush(如1秒内),同时又保证在数据量大的时候,写buffer一满就及时进行flush。
多HTable并发读:创建多个HTable客户端用于读操作,提高读数据的吞吐量
HTable参数设置:
批量读:通过调用HTable.get(Get)方法可以根据一个指定的row key获取一行记录,同样HBase提供了另一个方法:通过调用HTable.get(List)方法可以根据一个指定的row key列表,批量获取多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高而且网络传输RTT高的情景下可能带来明显的性能提升。
多线程并发:在客户端开启多个HTable读线程,每个读线程负责通过HTable对象进行get操作。
缓存查询结果:对于频繁查询HBase的应用场景,可以考虑在应用程序中做缓存,当有新的查询请求时,首先在缓存中查找,如果存在则直接返回,不再查询HBase;否则对HBase发起读请求查询,然后在应用程序中将查询结果缓存起来。至于缓存的替换策略,可以考虑LRU等常用的策略。
Blockcache:HBase上Regionserver的内存分为两个部分,一部分作为Memstore,主要用来写;另外一部分作为BlockCache,主要用于读。
写请求会先写入Memstore,Regionserver会给每个region提供一个Memstore,当Memstore满64MB以后,会启动 flush刷新到磁盘。当Memstore的总大小超过限制时(heapsize * hbase.regionserver.global.memstore.upperLimit * 0.9),会强行启动flush进程,从最大的Memstore开始flush直到低于限制。
读请求先到Memstore中查数据,查不到就到BlockCache中查,再查不到就会到磁盘上读,并把读的结果放入BlockCache。由于BlockCache采用的是LRU策略,因此BlockCache达到上限(heapsize * hfile.block.cache.size * 0.85)后,会启动淘汰机制,淘汰掉最老的一批数据。
一个Regionserver上有一个BlockCache和N个Memstore,它们的大小之和不能大于等于heapsize * 0.8,否则HBase不能启动。默认BlockCache为0.2,而Memstore为0.4。对于注重读响应时间的系统,可以将 BlockCache设大些,比如设置BlockCache=0.4,Memstore=0.39,以加大缓存的命中率。
HBase是一个没有单点故障的分布式系统,上层(HBase层)和底层(HDFS层)都通过一定的技术手段,保障了服务的可用性。上层HMaster一般都是高可用部署,而RegionServer如果出现宕机,region迁移的代价并不大,一般都在毫秒级别完成,所以对应用造成的影响也很有限;底层存储依赖于HDFS,数据本身默认也有3副本,数据存储上做到了多副本冗余,而且Hadoop 2.0以后NameNode的单点故障也被消除。所以,对于这样一个本身没有单点故障,数据又有多副本冗余的系统,再进行高可用的配置是否有这个必要?
为了保证 HBase 集群的高可靠性,HBase 支持多 Backup Master 设置,当Active Master 挂掉后,Backup Master 可以自动接管整个HBase的集群
vim /usr/local/hbase/conf/backup-masters
我这里用 Worker1 作为备用 master