[TOC]
# HBASE内部原理
## 1 系统架构
![](https://box.kancloud.cn/9aa44d384315ef5b5ed63f9ee328fe64_550x298.png)
### Client
1. 包含访问hbase的接口,client维护着一些cache来加快对hbase的访问,比如regione的位置信息。
### Zookeeper
1) 保证任何时候,集群中只有一个master
2) 存贮所有Region的寻址入口----root表在哪台服务器上。
3) 实时监控Region Server的状态,将Region server的上线和下线信息实时通知给Master
4) 存储Hbase的schema,包括有哪些table,每个table有哪些column family
### Master职责
1) 为Region server分配region
2) 负责region server的负载均衡
3) 发现失效的region server并重新分配其上的region
4) HDFS上的垃圾文件回收
5) 处理schema更新请求
### Region Server职责
1) Region server维护Master分配给它的region,处理对这些region的IO请求
2) Region server负责切分在运行过程中变得过大的region
> 可以看到,client访问hbase上数据的过程并不需要master参与(寻址访问zookeeper和region server,数据读写访问regione server),master仅仅维护者table和region的元数据信息,负载很低。
## 2 物理存储
### 1、整体结构
![](https://box.kancloud.cn/f59b2c308e80769d26d6582a7d2643c4_521x325.png)
1) Table中的所有行都按照row key的字典序排列。
2) Table 在行的方向上分割为多个Hregion。
3) region按大小分割的(默认10G),每个表一开始只有一个region,随着数据不断插入表,region不断增大,当增大到一个阀值的时候,Hregion就会等分会两个新的Hregion。当table中的行不断增多,就会有越来越多的Hregion。
4) Hregion是Hbase中分布式存储和负载均衡的最小单元。最小单元就表示不同的Hregion可以分布在不同的HRegion server上。但一个Hregion是不会拆分到多个server上的。
5) HRegion虽然是负载均衡的最小单元,但并不是物理存储的最小单元。
> 事实上,HRegion由一个或者多个Store组成,每个store保存一个column family。
> 每个Strore又由一个memStore和0至多个StoreFile组成。如上图
![](https://box.kancloud.cn/b52b09bab060c4441aa66288e206cfd2_306x226.png)
### 2、STORE FILE & HFILE结构
> StoreFile以HFile格式保存在HDFS上。
> 附:HFile的格式为:
![](https://box.kancloud.cn/cf0d2778695adfb951c5a13ba4c9b3c6_555x161.png)
> 首先HFile文件是不定长的,长度固定的只有其中的两块:Trailer和FileInfo。正如图中所示的,Trailer中有指针指向其他数 据块的起始点。
> File Info中记录了文件的一些Meta信息,例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY等。
> Data Index和Meta Index块记录了每个Data块和Meta块的起始点。
> Data Block是HBase I/O的基本单元,为了提高效率,HRegionServer中有基于LRU的Block Cache机制。每个Data块的大小可以在创建一个Table的时候通过参数指定,大号的Block有利于顺序Scan,小号Block利于随机查询。 每个Data块除了开头的Magic以外就是一个个KeyValue对拼接而成, Magic内容就是一些随机数字,目的是防止数据损坏。
> HFile里面的每个KeyValue对就是一个简单的byte数组。但是这个byte数组里面包含了很多项,并且有固定的结构。我们来看看里面的具体结构:
![](https://box.kancloud.cn/f20a6404efa2b70b84f682dba8c206f6_553x93.png)
> 开始是两个固定长度的数值,分别表示Key的长度和Value的长度。紧接着是Key,开始是固定长度的数值,表示RowKey的长度,紧接着是 RowKey,然后是固定长度的数值,表示Family的长度,然后是Family,接着是Qualifier,然后是两个固定长度的数值,表示Time Stamp和Key Type(Put/Delete)。Value部分没有这么复杂的结构,就是纯粹的二进制数据了。
> HFile分为六个部分:
1. Data Block 段–保存表中的数据,这部分可以被压缩
2. Meta Block 段 (可选的)–保存用户自定义的kv对,可以被压缩。
3. File Info 段–Hfile的元信息,不被压缩,用户也可以在这一部分添加自己的元信息。
4. Data Block Index 段–Data Block的索引。每条索引的key是被索引的block的第一条记录的key。
5. Meta Block Index段 (可选的)–Meta Block的索引。
6. Trailer–这一段是定长的。保存了每一段的偏移量,读取一个HFile时,会首先 读取Trailer,Trailer保存了每个段的起始位置(段的Magic Number用来做安全check),然后,DataBlock Index会被读取到内存中,这样,当检索某个key时,不需要扫描整个HFile,而只需从内存中找到key所在的block,通过一次磁盘io将整个 block读取到内存中,再找到需要的key。DataBlock Index采用LRU机制淘汰。
> HFile的Data Block,Meta Block通常采用压缩方式存储,压缩之后可以大大减少网络IO和磁盘IO,随之而来的开销当然是需要花费cpu进行压缩和解压缩。
> 目标Hfile的压缩支持两种方式:Gzip,Lzo。
### 3 Memstore与storefile
> 一个region由多个store组成,每个store包含一个列族的所有数据
> Store包括位于内存的memstore和位于硬盘的storefile
> 写操作先写入memstore,当memstore中的数据量达到某个阈值,Hregionserver启动flashcache进程写入storefile,每次写入形成单独一个storefile
> 当storefile大小超过一定阈值后,会把当前的region分割成两个,并由Hmaster分配给相应的region服务器,实现负载均衡
> 客户端检索数据时,先在memstore找,找不到再找storefile
### 4、HLog(WAL log)
> WAL 意为Write ahead log(http://en.wikipedia.org/wiki/Write-ahead_logging),类似mysql中的binlog,用来 做灾难恢复只用,Hlog记录数据的所有变更,一旦数据修改,就可以从log中进行恢复。
> 每个Region Server维护一个Hlog,而不是每个Region一个。这样不同region(来自不同table)的日志会混在一起,这样做的目的是不断追加单个文件相对于同时写多个文件而言,可以减少磁盘寻址次数,因此可以提高对table的写性能。带来的麻烦是,如果一台region server下线,为了恢复其上的region,需要将region server上的log进行拆分,然后分发到其它region server上进行恢复。
> HLog文件就是一个普通的Hadoop Sequence File:
1) HLog Sequence File 的Key是HLogKey对象,HLogKey中记录了写入数据的归属信息,除了table和region名字外,同时还包括 sequence number和timestamp,timestamp是”写入时间”,sequence number的起始值为0,或者是最近一次存入文件系统中sequence number。
2) HLog Sequece File的Value是HBase的KeyValue对象,即对应HFile中的KeyValue,可参见上文描述。
## 3 寻址机制
### 1、寻址示意图
![](https://box.kancloud.cn/df7668d48ac9fa1410602067eb31f0c7_506x286.png)
### 2、-ROOT-和.META.表结构
![](https://box.kancloud.cn/efd0ad53b9f4675deeb2f9997b1f94fb_425x144.png)
> .META.行记录结构
![](https://box.kancloud.cn/eaa82c10689d9119d6d99bbe83566ee8_425x276.png)
### 3、寻址流程
> 现在假设我们要从Table2里面插寻一条RowKey是RK10000的数据。那么我们应该遵循以下步骤:
1. 从.META.表里面查询哪个Region包含这条数据。
2. 获取管理这个Region的RegionServer地址。
3. 连接这个RegionServer, 查到这条数据。
> 系统如何找到某个row key (或者某个 row key range)所在的region
> bigtable 使用三层类似B+树的结构来保存region位置。
> 第一层是保存zookeeper里面的文件,它持有root region的位置。
> 第二层root region是.META.表的第一个region其中保存了.META.表其它region的位置。通过root region,我们就可以访问.META.表的数据。
> .META.是第三层,它是一个特殊的表,保存了hbase中所有数据表的region 位置信息。
> 说明:
1) root region永远不会被split,保证了最需要三次跳转,就能定位到任意region 。
2) META.表每行保存一个region的位置信息,row key 采用表名+表的最后一行编码而成。
3) 为了加快访问,.META.表的全部region都保存在内存中。
4) client会将查询过的位置信息保存缓存起来,缓存不会主动失效,因此如果client上的缓存全部失效,则需要进行最多6次网络来回,才能定位到正确的region(其中三次用来发现缓存失效,另外三次用来获取位置信息)。
### 4 读写过程
#### 1 读请求过程:
1) 客户端通过zookeeper以及root表和meta表找到目标数据所在的regionserver
2) 联系regionserver查询目标数据
3) regionserver定位到目标数据所在的region,发出查询请求
4) region先在memstore中查找,命中则返回
5) 如果在memstore中找不到,则在storefile中扫描(可能会扫描到很多的storefile----bloomfilter)
#### 2、写请求过程:
1) client向region server提交写请求
2) region server找到目标region
3) region检查数据是否与schema一致
4) 如果客户端没有指定版本,则获取当前系统时间作为数据版本
5) 将更新写入WAL log
6) 将更新写入Memstore
7) 判断Memstore的是否需要flush为Store文件。
> 细节描述:
* hbase使用MemStore和StoreFile存储对表的更新。
* 数据在更新时首先写入Log(WAL log)和内存(MemStore)中,MemStore中的数据是排序的,当MemStore累计到一定阈值时,就会创建一个新的MemStore,并 且将老的MemStore添加到flush队列,由单独的线程flush到磁盘上,成为一个StoreFile。于此同时,系统会在zookeeper中记录一个redo point,表示这个时刻之前的变更已经持久化了。
* 当系统出现意外时,可能导致内存(MemStore)中的数据丢失,此时使用Log(WAL log)来恢复checkpoint之后的数据。
* StoreFile是只读的,一旦创建后就不可以再修改。因此Hbase的更新其实是不断追加的操作。当一个Store中的StoreFile达到一定的阈值后,就会进行一次合并(minor_compact, major_compact),将对同一个key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小达到一定阈值后,又会对 StoreFile进行split,等分为两个StoreFile。
* 由于对表的更新是不断追加的,compact时,需要访问Store中全部的 StoreFile和MemStore,将他们按row key进行合并,由于StoreFile和MemStore都是经过排序的,并且StoreFile带有内存中索引,合并的过程还是比较快。
## 5 Region管理
### (1) region分配
> 任何时刻,一个region只能分配给一个region server。master记录了当前有哪些可用的region server。以及当前哪些region分配给了哪些region server,哪些region还没有分配。当需要分配的新的region,并且有一个region server上有可用空间时,master就给这个region server发送一个装载请求,把region分配给这个region server。region server得到请求后,就开始对此region提供服务。
### (2) region server上线
> master使用zookeeper来跟踪region server状态。当某个region server启动时,会首先在zookeeper上的server目录下建立代表自己的znode。由于master订阅了server目录上的变更消息,当server目录下的文件出现新增或删除操作时,master可以得到来自zookeeper的实时通知。因此一旦region server上线,master能马上得到消息。
### (3) region server下线
> 当region server下线时,它和zookeeper的会话断开,zookeeper而自动释放代表这台server的文件上的独占锁。master就可以确定:
1) region server和zookeeper之间的网络断开了。
2) region server挂了。
> 无论哪种情况,region server都无法继续为它的region提供服务了,此时master会删除server目录下代表这台region server的znode数据,并将这台region server的region分配给其它还活着的同志。
## 6 Master工作机制
### 1) master上线
> master启动进行以下步骤:
1) 从zookeeper上获取唯一一个代表active master的锁,用来阻止其它master成为master。
2) 扫描zookeeper上的server父节点,获得当前可用的region server列表。
3) 和每个region server通信,获得当前已分配的region和region server的对应关系。
4) 扫描.META.region的集合,计算得到当前还未分配的region,将他们放入待分配region列表。
### 2) master下线
> 由于master只维护表和region的元数据,而不参与表数据IO的过程,master下线仅导致所有元数据的修改被冻结(无法创建删除表,无法修改表的schema,无法进行region的负载均衡,无法处理region 上下线,无法进行region的合并,唯一例外的是region的split可以正常进行,因为只有region server参与),表的数据读写还可以正常进行。因此master下线短时间内对整个hbase集群没有影响。
> 从上线过程可以看到,master保存的信息全是可以冗余信息(都可以从系统其它地方收集到或者计算出来)
> 因此,一般hbase集群中总是有一个master在提供服务,还有一个以上的‘master’在等待时机抢占它的位置。
> 动手练习(增删改查)
## HBASE原理图
![](https://box.kancloud.cn/d9ded7633864b1f7f27e00940f7c73fe_1784x2056.png)
## 7 HBASE参数
### 配置优化
~~~
zookeeper.session.timeout
~~~
> 默认值:3分钟(180000ms)
> 说明:RegionServer与Zookeeper间的连接超时时间。当超时时间到后,ReigonServer会被Zookeeper从RS集群清单中移除,HMaster收到移除通知后,会对这台server负责的regions重新balance,让其他存活的RegionServer接管.
> 调优:
> 这个timeout决定了RegionServer是否能够及时的failover。设置成1分钟或更低,可以减少因等待超时而被延长的failover时间。
> 不过需要注意的是,对于一些Online应用,RegionServer从宕机到恢复时间本身就很短的(网络闪断,crash等故障,运维可快速介入),如果调低timeout时间,反而会得不偿失。因为当ReigonServer被正式从RS集群中移除时,HMaster就开始做balance了(让其他RS根据故障机器记录的WAL日志进行恢复)。当故障的RS在人工介入恢复后,这个balance动作是毫无意义的,反而会使负载不均匀,给RS带来更多负担。特别是那些固定分配regions的场景。
~~~
hbase.regionserver.handler.count
~~~
> 默认值:10
* 说明:RegionServer的请求处理IO线程数。
> 调优:
1. 这个参数的调优与内存息息相关。
2. 较少的IO线程,适用于处理单次请求内存消耗较高的Big PUT场景(大容量单次PUT或设置了较大cache的scan,均属于Big PUT)或ReigonServer的内存比较紧张的场景。
3. 较多的IO线程,适用于单次请求内存消耗低,TPS要求非常高的场景。设置该值的时候,以监控内存为主要参考。
4. 这里需要注意的是如果server的region数量很少,大量的请求都落在一个region上,因快速充满memstore触发flush导致的读写锁会影响全局TPS,不是IO线程数越高越好。
5. 压测时,开启Enabling RPC-level logging,可以同时监控每次请求的内存消耗和GC的状况,最后通过多次压测结果来合理调节IO线程数。
6. 这里是一个案例?Hadoop and HBase Optimization for Read Intensive Search Applications,作者在SSD的机器上设置IO线程数为100,仅供参考。
~~~
hbase.hregion.max.filesize
~~~
> 默认值:256M
* 说明:在当前ReigonServer上单个Reigon的最大存储空间,单个Region超过该值时,这个Region会被自动split成更小的region。
> 调优:
1. 小region对split和compaction友好,因为拆分region或compact小region里的storefile速度很快,内存占用低。缺点是split和compaction会很频繁。
2. 特别是数量较多的小region不停地split, compaction,会导致集群响应时间波动很大,region数量太多不仅给管理上带来麻烦,甚至会引发一些Hbase的bug。
3. 一般512以下的都算小region。
4. 大region,则不太适合经常split和compaction,因为做一次compact和split会产生较长时间的停顿,对应用的读写性能冲击非常大。此外,大region意味着较大的storefile,compaction时对内存也是一个挑战。
5. 当然,大region也有其用武之地。如果你的应用场景中,某个时间点的访问量较低,那么在此时做compact和split,既能顺利完成split和compaction,又能保证绝大多数时间平稳的读写性能。
6. 既然split和compaction如此影响性能,有没有办法去掉?
7. compaction是无法避免的,split倒是可以从自动调整为手动。
8. 只要通过将这个参数值调大到某个很难达到的值,比如100G,就可以间接禁用自动split(RegionServer不会对未到达100G的region做split)。
9. 再配合RegionSplitter这个工具,在需要split时,手动split。
10. 手动split在灵活性和稳定性上比起自动split要高很多,相反,管理成本增加不多,比较推荐online实时系统使用。
11. 内存方面,小region在设置memstore的大小值上比较灵活,大region则过大过小都不行,过大会导致flush时app的IO wait增高,过小则因store file过多影响读性能。
~~~
hbase.regionserver.global.memstore.upperLimit/lowerLimit
~~~
> 默认值:0.4/0.35
* upperlimit说明:hbase.hregion.memstore.flush.size 这个参数的作用是当单个Region内所有的memstore大小总和超过指定值时,flush该region的所有memstore。RegionServer的flush是通过将请求添加一个队列,模拟生产消费模式来异步处理的。那这里就有一个问题,当队列来不及消费,产生大量积压请求时,可能会导致内存陡增,最坏的情况是触发OOM。
* 这个参数的作用是防止内存占用过大,当ReigonServer内所有region的memstores所占用内存总和达到heap的40%时,HBase会强制block所有的更新并flush这些region以释放所有memstore占用的内存。
* lowerLimit说明: 同upperLimit,只不过lowerLimit在所有region的memstores所占用内存达到Heap的35%时,不flush所有的memstore。它会找一个memstore内存占用最大的region,做个别flush,此时写更新还是会被block。lowerLimit算是一个在所有region强制flush导致性能降低前的补救措施。在日志中,表现为 “** Flush thread woke up with memory above low water.”
> 调优:
* 这是一个Heap内存保护参数,默认值已经能适用大多数场景。
* 参数调整会影响读写,如果写的压力大导致经常超过这个阀值,则调小读缓存hfile.block.cache.size增大该阀值,或者Heap余量较多时,不修改读缓存大小。
* 如果在高压情况下,也没超过这个阀值,那么建议你适当调小这个阀值再做压测,确保触发次数不要太多,然后还有较多Heap余量的时候,调大hfile.block.cache.size提高读性能。
* 还有一种可能性是?hbase.hregion.memstore.flush.size保持不变,但RS维护了过多的region,要知道 region数量直接影响占用内存的大小。
hfile.block.cache.size
> 默认值:0.2
* 说明:storefile的读缓存占用Heap的大小百分比,0.2表示20%。该值直接影响数据读的性能。
* 调优:当然是越大越好,如果写比读少很多,开到0.4-0.5也没问题。如果读写较均衡,0.3左右。如果写比读多,果断默认吧。设置这个值的时候,你同时要参考?
> hbase.regionserver.global.memstore.upperLimit?,该值是memstore占heap的最大百分比,两个参数一个影响读,一个影响写。如果两值加起来超过80-90%,会有OOM的风险,谨慎设置。
~~~
hbase.hstore.blockingStoreFiles
~~~
默认值:7
* 说明:在flush时,当一个region中的Store(Coulmn Family)内有超过7个storefile时,则block所有的写请求进行compaction,以减少storefile数量。
* 调优:block写请求会严重影响当前regionServer的响应时间,但过多的storefile也会影响读性能。从实际应用来看,为了获取较平滑的响应时间,可将值设为无限大。如果能容忍响应时间出现较大的波峰波谷,那么默认或根据自身场景调整即可。
~~~
hbase.hregion.memstore.block.multiplier
~~~
> 默认值:2
* 说明:当一个region里的memstore占用内存大小超过hbase.hregion.memstore.flush.size两倍的大小时,block该region的所有请求,进行flush,释放内存。虽然我们设置了region所占用的memstores总内存大小,比如64M,但想象一下,在最后63.9M的时候,我Put了一个200M的数据,此时memstore的大小会瞬间暴涨到超过预期的hbase.hregion.memstore.flush.size的几倍。这个参数的作用是当memstore的大小增至超过hbase.hregion.memstore.flush.size 2倍时,block所有请求,遏制风险进一步扩大。
* 调优: 这个参数的默认值还是比较靠谱的。如果你预估你的正常应用场景(不包括异常)不会出现突发写或写的量可控,那么保持默认值即可。如果正常情况下,你的写请求量就会经常暴长到正常的几倍,那么你应该调大这个倍数并调整其他参数值,比如
> hfile.block.cache.sizehbase.regionserver.global.memstore.upperLimit/lowerLimit,以预留更多内存,防止HBase server OOM。
~~~
hbase.hregion.memstore.mslab.enabled
~~~
> 默认值:true
* 说明:减少因内存碎片导致的Full GC,提高整体性能。
* 调优:详见 http://kenwublog.com/avoid-full-gc-in-hbase-using-arena-allocation
### 其他
#### 启用LZO压缩
> LZO对比Hbase默认的GZip,前者性能较高,后者压缩比较高,具体参见?Using LZO Compression 。对于想提高HBase读写性能的开发者,采用LZO是比较好的选择。对于非常在乎存储空间的开发者,则建议保持默认。
> 不要在一张表里定义太多的Column Family
> Hbase目前不能良好的处理超过包含2-3个CF的表。因为某个CF在flush发生时,它邻近的CF也会因关联效应被触发flush,最终导致系统产生更多IO。
> 批量导入
> 在批量导入数据到Hbase前,你可以通过预先创建regions,来平衡数据的负载。详见?Table Creation: Pre-Creating Regions
> 避免CMS concurrent mode failure
> HBase使用CMS GC。默认触发GC的时机是当年老代内存达到90%的时候,这个百分比由 -XX:CMSInitiatingOccupancyFraction=N 这个参数来设置。concurrent mode failed发生在这样一个场景:
当年老代内存达到90%的时候,CMS开始进行并发垃圾收集,于此同时,新生代还在迅速不断地晋升对象到年老代。当年老代CMS还未完成并发标记时,年老代满了,悲剧就发生了。CMS因为没内存可用不得不暂停mark,并触发一次stop the world(挂起所有jvm线程),然后采用单线程拷贝方式清理所有垃圾对象。这个过程会非常漫长。为了避免出现concurrent mode failed,建议让GC在未到90%时,就触发。
通过设置?-XX:CMSInitiatingOccupancyFraction=N
这个百分比, 可以简单的这么计算。如果你的?hfile.block.cache.size 和?hbase.regionserver.global.memstore.upperLimit 加起来有60%(默认),那么你可以设置 70-80,一般高10%左右差不多。
### Hbase客户端优化
~~~
AutoFlush
~~~
* 将HTable的setAutoFlush设为false,可以支持客户端批量更新。即当Put填满客户端flush缓存时,才发送到服务端。
* 默认是true。
~~~
Scan Caching
~~~
* scanner一次缓存多少数据来scan(从服务端一次抓多少数据回来scan)。
* 默认值是 1,一次只取一条。
~~~
Scan Attribute Selection
~~~
* scan时建议指定需要的Column Family,减少通信量,否则scan操作默认会返回整个row的所有数据(所有Coulmn Family)。
~~~
Close ResultScanners
~~~
* 通过scan取完数据后,记得要关闭ResultScanner,否则RegionServer可能会出现问题(对应的Server资源无法释放)。
~~~
Optimal Loading of Row Keys
~~~
* 当你scan一张表的时候,返回结果只需要row key(不需要CF, qualifier,values,timestaps)时,你可以在scan实例中添加一个filterList,并设置 MUST_PASS_ALL操作,filterList中add?FirstKeyOnlyFilter或KeyOnlyFilter。这样可以减少网络通信量。
~~~
Turn off WAL on Puts
~~~
* 当Put某些非重要数据时,你可以设置writeToWAL(false),来进一步提高写性能。writeToWAL(false)会在Put时放弃写WAL log。风险是,当RegionServer宕机时,可能你刚才Put的那些数据会丢失,且无法恢复。
~~~
启用Bloom Filter
~~~
* Bloom Filter通过空间换时间,提高读操作性能。
## 8 HBASE性能优化
1) hbase.hregion.max.filesize应该设置多少合适
2) autoflush=false的影响
3) 从性能的角度谈table中family和qualifier的设置
4) hbase.regionserver.handler.count详解
### 1 hbase.hregion.max.filesize应该设置多少合适
~~~
默认值:256M
~~~
> 说明:
* Maximum HStoreFile size. If any one of a column families' HStoreFiles has grown to exceed this value, the hosting HRegion is split in two.
* HStoreFile的最大值。如果任何一个Column Family(或者说HStore)的HStoreFiles的大小超过这个值,那么,其所属的HRegion就会Split成两个。
> 调优:
* hbase中hfile的默认最大值(hbase.hregion.max.filesize)是256MB,而google的bigtable论文中对tablet的最大值也推荐为100-200MB,这个大小有什么秘密呢?
* 众所周知hbase中数据一开始会写入memstore,当memstore满64MB以后,会flush到disk上而成为storefile。当storefile数量超过3时,会启动compaction过程将它们合并为一个storefile。这个过程中会删除一些timestamp过期的数据,比如update的数据。而当合并后的storefile大小大于hfile默认最大值时,会触发split动作,将它切分成两个region。
* lz进行了持续insert压力测试,并设置了不同的hbase.hregion.max.filesize,根据结果得到如下结论:值越小,平均吞吐量越大,但吞吐量越不稳定;值越大,平均吞吐量越小,吞吐量不稳定的时间相对更小。
> 为什么会这样呢?推论如下:
1) 当hbase.hregion.max.filesize比较小时,触发split的机率更大,而split的时候会将region offline,因此在split结束的时间前,访问该region的请求将被block住,客户端自我block的时间默认为1s。当大量的region同时发生split时,系统的整体访问服务将大受影响。因此容易出现吞吐量及响应时间的不稳定现象
2) 当hbase.hregion.max.filesize比较大时,单个region中触发split的机率较小,大量region同时触发split的机率也较小,因此吞吐量较之小hfile尺寸更加稳定些。但是由于长期得不到split,因此同一个region内发生多次compaction的机会增加了。compaction的原理是将原有数据读一遍并重写一遍到hdfs上,然后再删除原有数据。无疑这种行为会降低以io为瓶颈的系统的速度,因此平均吞吐量会受到一些影响而下降。
> 综合以上两种情况,hbase.hregion.max.filesize不宜过大或过小,256MB或许是一个更理想的经验参数。对于离线型的应用,调整为128MB会更加合适一些,而在线应用除非对split机制进行改造,否则不应该低于256MB
### 2 autoflush=false的影响
> 无论是官方还是很多blog都提倡为了提高hbase的写入速度而在应用代码中设置autoflush=false,然后lz认为在在线应用中应该谨慎进行该设置。原因如下:
1) autoflush=false的原理是当客户端提交delete或put请求时,将该请求在客户端缓存,直到数据超过2M(hbase.client.write.buffer决定)或用户执行了hbase.flushcommits()时才向regionserver提交请求。因此即使htable.put()执行返回成功,也并非说明请求真的成功了。假如还没有达到该缓存而client崩溃,该部分数据将由于未发送到regionserver而丢失。这对于零容忍的在线服务是不可接受的。
2) autoflush=true虽然会让写入速度下降2-3倍,但是对于很多在线应用来说这都是必须打开的,也正是hbase为什么让它默认值为true的原因。当该值为true时,每次请求都会发往regionserver,而regionserver接收到请求后第一件事就是写hlog,因此对io的要求是非常高的,为了提高hbase的写入速度,应该尽可能高地提高io吞吐量,比如增加磁盘、使用raid卡、减少replication因子数等
### 3 从性能的角度谈table中family和qualifier的设置
> 对于传统关系型数据库中的一张table,在业务转换到hbase上建模时,从性能的角度应该如何设置family和qualifier呢?
> 最极端的,①每一列都设置成一个family,②一个表仅有一个family,所有列都是其中的一个qualifier,那么有什么区别呢?
> 从读的方面考虑:
* family越多,那么获取每一个cell数据的优势越明显,因为io和网络都减少了。
* 如果只有一个family,那么每一次读都会读取当前rowkey的所有数据,网络和io上会有一些损失。
* 当然如果要获取的是固定的几列数据,那么把这几列写到一个family中比分别设置family要更好,因为只需一次请求就能拿回所有数据。
> 从写的角度考虑:
1) 内存方面来说,对于一个Region,会为每一个表的每一个Family分配一个Store,而每一个Store,都会分配一个MemStore,所以更多的family会消耗更多的内存。
2) 从flush和compaction方面说,目前版本的hbase,在flush和compaction都是以region为单位的,也就是说当一个family达到flush条件时,该region的所有family所属的memstore都会flush一次,即使memstore中只有很少的数据也会触发flush而生成小文件。这样就增加了compaction发生的机率,而compaction也是以region为单位的,这样就很容易发生compaction风暴从而降低系统的整体吞吐量。
3) 从split方面考虑,由于hfile是以family为单位的,因此对于多个family来说,数据被分散到了更多的hfile中,减小了split发生的机率。这是把双刃剑。更少的split会导致该region的体积比较大,由于balance是以region的数目而不是大小为单位来进行的,因此可能会导致balance失效。而从好的方面来说,更少的split会让系统提供更加稳定的在线服务。而坏处我们可以通过在请求的低谷时间进行人工的split和balance来避免掉。
> 因此对于写比较多的系统,如果是离线应该,我们尽量只用一个family好了,但如果是在线应用,那还是应该根据应用的情况合理地分配family。
### 4 hbase.regionserver.handler.count
* RegionServer端开启的RPC监听器实例个数,也即RegionServer能够处理的IO请求线程数。默认是10.
* 此参数与内存息息相关。该值设置的时候,以监控内存为主要参考。
* 对于 单次请求内存消耗较高的Big PUT场景(大容量单次PUT或设置了较大cache的scan,均属于Big PUT)或ReigonServer的内存比较紧张的场景,可以设置的相对较小。
* 对于 单次请求内存消耗低,TPS(TransactionPerSecond,每秒事务处理量)要求非常高的场景,可以设置的相对大些。
- hadoop
- linux基础
- Linux入门
- Linux进阶
- shell
- Zookeeper
- Zookeeper简介及部署
- Zookeeper使用及API
- Redis
- Redis简介安装部署
- Redis使用及API
- Java高级增强
- Java多线程增强
- Maven简介及搭建
- Hive
- Hive简介及安装
- Hive操作
- HIve常用函数
- Hive数据类型
- Flume
- Flume简介及安装
- flume 拦截器(interceptor)
- azkaban
- azKaban简介及安装
- Sqoop
- Sqoop简介及安装
- HDFS
- HDFS原理
- HDFS操作API
- MAPREDUCE原理
- MAPREDUCE图片资源
- MAPREDUCE加强
- HBASE
- HBASE简介及安装
- HBASE操作及API
- HBASE内部原理
- Storm
- Storm简介及安装
- Storm原理
- kafka
- kafka简介及安装
- kafka常用操作及API
- kafka原理
- kafka配置详解
- Scala
- Scala简介及安装
- Scala基础语法
- Scala实战