[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,每秒事务处理量)要求非常高的场景,可以设置的相对大些。