- 本篇文章不是自己总结的,而是参考资料上进行摘抄的,本文涉及到HBase的Region拆分合并以及HFile拆分合并以及其他的调优参考,如果你参考本文的话,请一定在尝试之后在修改配置,本文只是自己的一个学习记录方便以后查阅
- 参考了 HBase权威指南 和 HBase不睡觉书
尽信书不如无书,在使用的时候需要先测试!!!!!
调大堆内存
- 默认RegionServer的内存是1GB,而MemStore默认是占百分之四十,所以MemStore才有400MB空间,在实际应用中,很容易就会被写阻塞了,可以通过指定HBASE_HEAPSIZE参数来调整所有HBase实例占用的内存大小,不管是Master还是RegionServer
#在hbase-env.sh中
# The maximum amount of heap to use. Default is left to JVM default.
export HBASE_HEAPSIZE=4G #原来是1G,现在修改为4G
- 上面参数会影响整个HBase实例,包括master和region,这样的话master和RegionServer都会占用4GB
设置Master和RegionServer的内存大小
export HBASE_MASTER_OPTS="$HBASE_MASTER_OPTS -Xms4g -Xmx4g"
export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS -Xms8g -Xmx8g"
- 上面是将master的JVM内存设置为4G,RegionServer的内存设置为8G,但是你需要留出内存的百分之十给操作系统
- 参考:16G的机子上有RegionServer,MR,DN服务,那么2G需要给系统,8G给MR,4G给RegionServer,1GDN,1GtaskTracker
Full GC
- 随着堆内存越大,那么Full GC回收的时间就越长,在Full GC的时候JVM会停止响应任何请求,这种暂停被称为STW(stop-the-world)
- 当zk像往常一样通过心跳来检测RegionServer节点是否存活的时候,发现已经很久没有来自RegionServer的回应,会直接将这个RegionServer标记为宕机,等到这台RegionServer结束Full GC后回来,去汇报ZK的时候,发现自己已经挂了,为了防止脑裂问题的发生,只能自己停止自己,这种场景被称为RegionServer自杀(朱丽叶暂停),所以我们要设定好GC的回收策略,避免长时间的Full GC,或者是尽量减少Full GC时间
GC回收策略优化
- 数据都存放在RegionServer上,所以问题一般出在RegionServer上,Master只是做一些管理操作
-
JVM的4种GC回收器
- 串行回收器SerialGC
- 并行回收器ParallelGC,主要针对年轻代进行优化(jdk8默认策略)
- 并发回收器ConcMarkSweepGC,CMS,主要针对年老带进行优化
- G1GC回收器,针对大内存进行优化(内存>=32)
-
ParallelGC和CMS的组合方案
- 并行回收器比串行的Full GC时间较短,而Full GC对于RegionServer的影响是很大的,所以我们采用并行回收器
- 并发回收器主要是减少老年代的暂停时间,可以保证应用不停止的情况下进行收集,缺点是每次都会落下一些"浮动垃圾",这些浮动垃圾只能在下次垃圾回收的时候被回收,这个是可以忍受的
- 所以基于上面的描述,HBase配置是年轻代使用并行回收器,年老代使用并发回收器
- 修改
#habse-env.sh
export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS -Xms8g -Xmx8g -XX:+UseParNewGCXX:+UseConcMarkSweepGC"
-
G1GC方案
-
这个方案需要你的RegionServer的内存大于4GB并且大于JDK1.7.0_04版本,因为这是jdk这个版本新加入的策略,这个方案适用于堆内存很大的情况,引入G1GC策略的原因是:就算采用CMS策略,还是不能避免FullGC,因为两种情况还是会引发CMS的Full GC
- 当你的年老代不足的时候,年轻代又在不断的移到年老代的时候,就会引发Full GC
- 当被回收掉的内存空间太碎太细小,导致新加入年老代的对象放不进去,就会触发Full GC整理空间
- G1GC通过把对内存划分为多个区域,然后对各个区域单独进行GC,这样整体的Full GC可以被最大程度的避免,这种策略还可以通过手动指定MaxGCPauseMillis控制一旦发生Full GC的时候的最大暂停时间,避免时间太长造成RegionServer自杀
- 修改
export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS -Xms8g -Xmx8g -XX:+UseG1GC -XX:MaxGCPauseMillis=100"
-
32G,64G才算是大内存,当内存在4G或者32G之间的时候,需要测试方案的可行性来决定采用哪种方案,下面是一些参考意见
- 如果RegionServer内存小于4GB,就不需要参考G1GC策略,直接采用并行加并发策略
- 如果RegionServer内存大于32GB,就推荐G1GC策略
- 在测试的时候把调试参数加上,这样就可以看到试验的量化结果
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy
Memstore的专属JVM策略MSLAB
-
现在内存越来越大,每增加一GB,平均会增加8到10秒的FullGC时间,所以当堆内存很大的时候,Full GC会消耗很多时间,对于CMS算法依旧会触发Full GC,原因是
- 同步模式失败:在CMS还没有把垃圾收集完的时候空间还没有完全释放,在这个时候如果新生代的对象过快的转换为老年代的对象时发现老年代的可用空间不够用了,此时收集器就会停止收集过程,转为单线程的STW暂停,将又触发Full GC了,不过这个过程可以设置:
-XX:CMSInitiatingOccupancyFraction=N来缓解,N代表了当JVM启动垃圾回收时的堆内存占用百分比,设置的越小,JVM越早启动垃圾回收进程
- 由于碎片化造成的失败:当前要从新生代提升到老年代的对象比老年代的所有可以使用的连续的内存空间都大,比如老年代有500MB的空间可用,但是都是1KB的碎片空间,现在有一个2KB的对象要提升到老年代却发现没有一个空间可以插入,这时就会触发SWT暂停进行FullGC,这种碎片化情况通过设置
-XX:CMSInitiatingOccupancyFraction=N是不起作用的,因为CMS只做回收不做合并,所以RegionServer启动久了肯定会遇到Full GC
-
为什么会产生碎片化空间
- MemStore是会定期刷写HFile的,在刷写的同时,这块MemStore所占用的空间会被标记为待回收,一旦被回收了,这部分内存就可以再次被使用,但是由于JVM分配对象都是按顺序分配下去的,所以内存空间使用了一段时间后情况就是这样的
![markdown_img_paste_20181213141959919 markdown_img_paste_20181213141959919]()
假设红色大小为1KB,此时有2KB的新生代升级为老生代,但是JVM找不到连续的2KB内存空间去存放新对象,因为被刷写的内存只是被标记还没有释放,JVM这时候只好停止一切请求,然后将内存空间进行重新排列,这个排列的时间随着内存空间的增大而增大,当内存空间足够大的时候,暂停的时间足以让zk认为RegionServer挂了
- JVM为了避免这个问题有一个基于线程的解决方案,叫TLAB(thread-local allocation buffer),当你使用TLAB的时候,每一个线程都会分配一个固定大小的内存空间,专门给这个线程使用,当线程用完这个空间后再新申请的空间还是这么大,这样就不会出现特别小的碎片空间,基本所有的对象都可以有地方存放,缺点就是无论线程里面有没有对象需要占用这么大的内存,都会开辟这么大,有很多内存都是闲置的,内存空间利用率会降低,依次避免Full GC(即每次申请空间一致,避免申请的不同空间使用后的差值造成碎片)
- 但是HBase并不能使用TLAB,因为HBase中多个Region是被一个线程管理的,多个MemStore占用的空间还是无法合理的分开,于是HBase自己实现了一套以MemStore为最小单元的内存管理机制,称为MSLAB(MemStore-Local Allocation Buffers),这套机制沿袭了TLAB的实现思路,只不过内存空间是由MemStore来分配的
-
MSLAB实现
- 加入chunk,即一块2MB大小的chunk
- RegionServer中维护着一个全局的MemStoreChunkPool实例,是一个chunk池
- 每个MemStore实例里面有一个MemStoreLAB实例
- 每个MemStore接收到KeyValue数据的时间先从ChunkPool中申请一个chunk,然后放到整个chunk里面
- 如果chunk放满了,就重新申请一个chunk
- 如果MemStore因为刷写而释放内存,则按chunk来清空内存
- 由此可以看出堆内存被chunk区分为规则的空间,这样就消除了小 碎片引起的无法插入数据问题,但是会降低内存利用率,因为就算你的 chunk里面只放1KB的数据,这个chunk也要占2MB的大小.整个开销对于Full GC来说还是可以忍的
-
MSLAB相关参数
-
hbase.hregion.memstore.mslab.enabled:设置为true,即打开 MSLAB,默认为true
-
hbase.hregion.memstore.mslab.chunksize:每个chunk的大 小,默认为2048*1024 即2MB
-
hbase.hregion.memstore.mslab.max.allocation:能放入chunk 的最大单元格大小,默认为256KB,已经很大了
-
hbase.hregion.memstore.chunkpool.maxsize:在整个memstore 可以占用的堆内存中,chunkPool占用的比例,该值为一个百分 比,取值范围为0.0~1.0。默认值为0.0
-
hbase.hregion.memstore.chunkpool.initialsize:在 RegionServer启动的时候可以预分配一些空的chunk出来放到 chunkPool里面待使用.该值就代表了预分配的chunk占总的 chunkPool的比例,该值为一个百分比,取值范围为0.0~1.0,默 认值为0.0
Region的拆分
- Region就是一段数据的集合,当Region太大的时候就会拆分它,拆分Region的根本的原因是Region大了那么读取效率就低了
-
region自动拆分ConstantSizeRegionSplitPolicy
-
最早的0.94左右版本的时候只有一种拆分策略就是ConstantSizeRegionSplitPolicy,即计算Region的到小来达到拆分的目的,用到的参数是
<property>
<name>hbase.hregion.max.filesize</name>
<value>10737418240</value>
</property>
- 即如果达到10GB就会将这个Region拆分为两个
-
region自动拆分IncreasingToUpperBoundRegionSplitPolicy
- 之后就有了
IncreasingToUpperBoundRegionSplitPolicy拆分策略,该策略是限制不断增长的文件尺寸,文件尺寸限制是动态的
Math.min(tableRegionCount^3 * initialsize, defaultRegionMaxFileSize)
-
- tableRegionCount:表在所有RegionServer上所拥有的Region数量总和
- initiaSize:是
hbase.increasing.policy.initial.size这个数值,如果没有定义这个属性那么就用memstore的刷写大小的2倍
- defaultRegionMaxFileSize:即Region 的最大大小
- 假如HFile的刷写大小为128MB.当开始只有一个文件的时候文件尺寸上限为1^3*128*2=256MB,当是两个文件件的时候,最大为2^3*128*2= 2048MB,以此类推直到拥有四个文件的的时候,计算出来的值为16GB,已经超过最大Region大小了,所以之后Region数量在增加,文件大小上限也不会增加了
-
region自动拆分KeyPrefixRegionSplitPolicy
- 这个类是前面介绍的子类,在前面拆分策略上增加了拆分点(即Region被拆分处的rowkey),保证了有相同前缀的rowkey拆分到一个region中
KeyPrefixRegionSplitPolicy.prefix_length rowkey:前缀长度
- 该策略会根据定义的长度来截取rowkey作为分组的依据,同一组的数据不会被划分到不同的region上
- 这种策略对于只有一两个前缀的数据,那么根本不需要用这个,这个适用场景在于数据有多种前缀,并且查询多是针对前缀,比较少跨越多个前缀来查询数据
-
region自动拆分DelimitedKeyPrefixRegionSplitPolicy
- 跟前面介绍的是同一个父类,同样是根据rowkey进行切分,不同的是前面介绍的前缀策略是根据rowkey的固定前几个字符来进行判断的,而这个是根据分隔符来判断的,所以这个策略需要的参数就是你定义的rowkey 的分隔符
DelimitedKeyPrefixRegionSplitPolicy.delimiter : 前缀分隔符
-
region自动拆分BusyRegionSplitPolicy
-
region自动拆分DisabledRegionSplitPolicy
- 即代表region永远不拆分,但还是可以通过手动拆分region的,那么他有什么作用呢?
- 当数据进入HBase时肯定是往一个region中放,当达到阀值之后,region就开始拆分,但是如果是巨量数据写入就会造成一边拆一边写的情况,如果事先知道这个表按照什么策略来拆分region的话,那么你就可以使用此策略停止region的拆分,然后手动的去拆分region
-
region预拆分
- 就是在建表的时候就定义好了拆分点的算法,使用
RegionSplitter类来创建表,并传入拆分点算法就可以在建表的同时定义拆分点算法
- 使用:建立一个表其region数量一致保持是10个
hbase(main):005:0> create 'mysplittable','cf',{NUMREGIONS=>10,SPLITALGO=>'HexStringSplit'}
- NUMREGIONS是指你的region数目,而SPLITALGO是切分算法,扫描meta表,查看region区域
STARTKEY => '', ENDKEY => '19999999'
STARTKEY => '19999999', ENDKEY => '33333332'
STARTKEY => '33333332', ENDKEY => '4ccccccb'
STARTKEY => '4ccccccb', ENDKEY => '66666664'
STARTKEY => '66666664', ENDKEY => '7ffffffd'
STARTKEY => '7ffffffd', ENDKEY => '99999996'
STARTKEY => '99999996', ENDKEY => 'b333332f'
STARTKEY => 'b333332f', ENDKEY => 'ccccccc8'
STARTKEY => 'ccccccc8', ENDKEY => 'e6666661'
STARTKEY => 'e6666661', ENDKEY => ''
- HexStringSplit:就是将00000000到FFFFFFFF之前的数据按照你给定的region与切分数切分为不同region
- UniformSplit:和 HexStringSplit差不多,唯一区别是startkey和endkey不是String了而是byte[]
- 还可以通过实现SplitAlgorithm接口实现自己的拆分算法
- 在hbase shell界面列举出了如何进行预拆分,如下图
![markdown_img_paste_20181213183930784 markdown_img_paste_20181213183930784]()
![markdown_img_paste_20181213184128368 markdown_img_paste_20181213184128368]()
Region的合并
![markdown_img_paste_20181213185446826 markdown_img_paste_20181213185446826]()
压缩
- HBase支持大量压缩算法,并且可以支持列族上的数据压缩,一般是推荐开启压缩的,因为CPU压缩和解压缩消耗的时间比从磁盘中读取和写入更多数据消耗的时间更短
- 可用编码器:Snappy,LZO,GZIP...
- 对于HBase配置压缩方式可以百度一下
BlockCache优化
| 区域名称 |
占用比例 |
说明 |
| single-access |
25% |
单次读取区,block被读出后先放入此区域,当被读到多次后,升级到下一个区域 |
| multi-access |
50% |
多次读取区,当一个被缓存到单词读取区后又被多次访问会升级到这个区 |
| in-memeory |
25% |
这个区域跟block被访问了几次没有关系,它只存放哪些被设置了IN-MEMEORY=TRUE的列族 中读取出来的block |
![markdown_img_paste_20181213200341639 markdown_img_paste_20181213200341639]()
- 并不是说列族属性被标记为true,列族就被放入内存,只是说被标记为true的列族中的数据一开始就会被放入in-memory区域,这个区域的缓存有最高的存活时间,在需要淘汰block的时候,这个区域的block是最后被考虑到的
- 调整大小,但是不能关闭
<property>
<name>hfile.block.cache.size</name>
<value>0.4</value>
</property>
- 既然是基于堆内存的,那么势必在会造成Full GC
![markdown_img_paste_20181213205525548 markdown_img_paste_20181213205525548]()
- BucketCache对内存地址的管理:它自己来划分内存空 间、自己来管理内存空间,Block放进去的时候是考虑到offset偏移量 的,所以内存碎片少,发生GC的时间很短
- 其中file是指ssd硬盘
- BucketCache用到的配置项如下
alter 'test',CONFIGURATION=>{CACHE_DATA_IN_L1 => 'true'}
- 意思是只缓存在一级缓存(LRUCache)中,不使用二级缓存 (BucketCache)
-
相关配置如下
-
hbase.bucketcache.ioengine:使用的存储介质,可选值为 heap、offheap、file.默认为offheap。
-
hbase.bucketcache.combinedcache.enabled:是否打开组合模 式(CombinedBlockCache)默认为true
-
hbase.bucketcache.size:BucketCache所占的大小
- 如果设置为0.0~1.0,则代表了占堆内存的百分比.如果是大于1的值,则代表实际的BucketCache的大小单位为MB.默认值为0.0,即关闭BucketCache
-
-XX:MaxDirectMemorySize:这个参数是JVM启动的参数,如果不配置这个参数,JVM会按 需索取堆外内存,如果配置了这个参数,你可以定义JVM可以 获得的堆外内存上限,这个参数值必须比hbase.bucketcache.size大
-
组合模式
- 说就是把不同类型的Block分别放到 LRUCache和BucketCache中
- Index Block和Bloom Block会被放到LRUCache中。Data Block被直 接放到BucketCache中,所以数据会去LRUCache查询一下,然后再去 BucketCache中查询真正的数据。其实这种实现是一种更合理的二级缓 存,数据从一级缓存到二级缓存最后到硬盘,数据是从小到大,存储介 质也是由快到慢。考虑到成本和性能的组合,比较合理的介质是: LRUCache使用内存->BuckectCache使用SSD->HFile使用机械硬盘
MemStore的优化
- 所有的写操作在写入到磁盘上之前都会先放到MemStore上,直到MemStore被刷写到磁盘上,数据才真正的持久化到硬盘上,如果开启了BlockCache,那么读取数据的时候会先查询BlockCache,当发现没有的时候,才会去查询MemStore+HFile的数据,由于有些数据还未被刷写到HFile中,所以MemStore+HFile才是所有数据的集合,MemStore的目的是为了维持数据结构而并非加速写入,因为HDFS文件不支持修改,为了维持HBase数据的顺序,所必须先对数据进行整理后再持久化到HDFS上
- MemStore的刷写
- 数据进入HBase,在写入硬盘之前,会先暂存在内存中,数据不能一直驻留在内存,当数据从内存写到硬盘上的时候,这个动作叫做MemStore的刷写
-
什么时候会触发刷写机制?
- 大小达到刷写阀值:占用内存大小超过hbase.hregion.memstore.flush.size配置值就会导致刷写,默认是128MB,即HDFS块大小,刷写就会生成一个HFile.因为刷写是定期检查的,所以无法及时的在数据达到阀值的时候触发刷写,如果数据增长太快,而且超过了配置值,这时候就会触发阻塞机制,造成无法写入数据到MemStore
- 阻塞机制:
<property>
<name>hbase.hregion.memstore.block.multiplier</name>
<value>4</value>
</property>
<property>
<name>hbase.hregion.memstore.flush.size</name>
<value>134217728</value>
</property>
- 默认的阻塞阀值是上面两个配置项的乘积,如果在下一次刷写检查到来之前就达到了这个阀值,就会立刻触发一次刷写,这次刷写不仅仅是内存中的数据了,并且还会在刷写的时候同时阻塞所有写入Store的写请求.所以如果遇到了这的性能问题,可以调大刷写值,但是要同时参考HFile的相关参数设置,之后会说
- 整个RegionServer和memstore总和达到阀值
- 阀值计算
hbase.regionserver.global.memstore.size.lower.limit全局memstore刷写下 限以取值范围在0.0~1.0,默认为0.95
*
hbase_heapsize(RegionServer占用的堆内存大小)* hbase.regionserver.global.memstore.size
hbase.regionserver.global.memstore.size.lower.limit=0.95
hbase_heapsize* hbase.regionserver.global.memstore.size=16G*0.4
触发刷写阀值为:16*0.4*0.95=6.08,触发阻塞阀值为:16*0.4=6.4G
所以当memstore到达6.08G会触发强制刷写,当达到6.4G时会阻塞HBase集群的写入
maxLogs计算公式: Math.max(32,(regionserverHeapSize * MemStoreSizeRatiio * 2 / logRollSize))
- 当wal数量大于这个值就会触发memstore刷写以便创造新的memstore内存空间用来加载WAL中的数据,同时HBase会给出一个info日志,只是提醒你达到wal滚动条件了
Too many wals,counts=34,max=32,forcing flish of...
<property>
<name>hbase.regionserver.optionalcacheflushinterval</name>
<value>3600000</value>
</property>
- 默认1小时,如果设定为0,则意味着关闭定时自动刷写
- 手动触发flush:admin类和hbaseshell都有对应的操作方法flush xxx
HFile的合并
- HFile经常合并的原因是,HFile存储在硬盘上,硬盘需要寻址,HFile一多,性能势必下降,为了防止浪费更多的寻址时间,我们就需要合并操作
- HFile合并操作就是在一个Store里面找到需要合并的HFile,然后把他们合并起来,最后移除碎片文件
HFile合并策略
-
Minor Compaction和Major Compaction
- Minor:将Store里的HFile合并为一个HFile.删除TTL数据,但是不会删除手动删除的数据,触发率较高
- Major:将Store里的HFile合并为一个HFile.删除TTL,并且删除手动删除数据和超过maxVersion的版本数据,触发率较低
- Major Compaction只是把一个Store中的HFile合并为一个HFile
-
0.96之后的合并策略ExploringCompactionPolicy
- 是把所有的HFile都遍历一遍之后才会去考虑合并哪个HFile,符合合并条件的计算公式是
该文件 < (所有文件大小总和 - 该文件大小) * 比例因子
- 如果该文件大小小于
hbase.hstore.compaction.min.size,即最小合并大小,那么就直接进入待合并状态不需要套用公式,如果hbase.hstore.compaction.min.size没有配置就使用刷写大小hbase.hregion.memstore.flush.size限制,如果满足公式或者配置项任何一个就会进入待合并状态
- 被挑选的文件除了达到公式和最小合并大小的任一要求外,还需要组合内包含的文件数必须大于于hbase.hstore.compaction.min,小于 hbase.hstore.compaction.max,组合即HFile构成的集合
- 挑选完组合后,比较那个文件组合包含的文件更多,就合并哪个组合,如果出现平局,就筛选文件尺寸总和更小的组合
- 默认hbase.hstore.compaction.max=10 , hbase.hstore.compaction.min = 3
- 比方说有十个HFile,其中有5个满足条件被标记为待合并,因为我们的
hbase.hstore.compaction.min为3,设hbase.hstore.compaction.max=4,所以5个HFile就进行排列组合,比如12345五个HFile,会组成
123
1234
1235
125
...
- 每个组的最小HFile数量不能小于3,并且不能大于5,之后就找到HFile文件最多的一组,就开始合并,如果出现了两组一样多HFile文件的组,就比较两组的总大小,哪个组小就合并哪个,即多文件小尺寸优先合并
-
FIFOCompactionPolicy
-
DateTieredCompactionPolicy
- DateTieredCompactionPolicy解决的是一个基本的问题:最新的数据最 有可能被读到
- 应用场景举例:即发朋友圈,新发的朋友圈是最有可能被别人看的,而发表很长时间之后的朋友圈,基本没人阅读,所以朋友圈的HFile就会大概分为两种一个新发表的数据和旧发表的数据,而这个策略就是将新的和新的合并,旧的再一起合并,并且数据是改动不频繁的
- 配置
hbase.hstore.compaction.date.tiered.base.window.millis: 基本的时间窗口时长,默认是6小时。即从现在到6小时之内的HFile都在同一个时间窗口里 面,即这些文件都在最新的时间窗口里面,类似"时间分区"
hbase.hstore.compaction.date.tiered.windows.per.tier:层 次的增长倍数。分层的时候,越老的时间窗口越宽
hbase.hstore.compaction.date.tiered.max.tier.age.millis: 最老的层次时间。当文件太老了,老到超过这里所定义的时间范 围就直接不合并了
基本窗口宽度 (hbase.hstore.compaction.date.tiered.base.window.millis) = 1
最小合并数量(hbase.hstore.compaction.min) = 3
层次增长倍数 (hbase.hstore.compaction.date.tiered.windows.per.tier) = 2
- 把当前Store中的HFile从旧到新排列,然后画上时间窗口的分界 线,就变成如图
![markdown_img_paste_20181214140416557 markdown_img_paste_20181214140416557]()
- 可以看到只有第三个时间窗口中的HFile数量达到了最小合并数量即3个所以第三个时间窗口中 的文件会被合并
- 上面图是正好避开时间点的,如果一个HFile跨越时间线,那么HFile是计入更老的窗口的,比如
![markdown_img_paste_20181214140538343 markdown_img_paste_20181214140538343]()
- 这种策略如果达到合并条件,使用的合并策略是之前提到的一种,默认为ExploringCompactionPolicy
-
这种策略适用于
- 经常读写最近数据的系统,或者说这个系统专注于最新的数据
- 更适用 于那些基本不删除数据的系统
- 数据根据时间排序存储
- 数据的修改频率很有限,或者只修改最近的数据,基本不删除数 据
![markdown_img_paste_20181214141320991 markdown_img_paste_20181214141320991]()
- 如上图,当memstore刷写HFile的时候首先刷到L0区,当L0达到一定阀值的时候,就会将L0读出来放入Strips区域,Strips内的区域个数是可以自定义的,区域是按照行键划分数据的.
-
它解决了什么问题?
- 通过增加L0层,等于增加了一层缓冲,让合并更缓和
- 严格按照行键划分Strips,可以提高查询速度的稳定性
- Strips的各个区域可以单独的执行Major Compaction,比如a-f执行合并而g-m不受影响
- 这个策略特点是稳定
-
适用于什么场景?
- Region要大,这种策略实际上就是把Region给细分成一个个 Stripe,Region大小小于2G不考虑
- Rowkey要具有统一格式,能够均匀分布,因为是按照行键划分的
compaction的吞吐量限制参数
- 由于compaction机制会造成HBase出现IO突然降低的情况,但是compaction本身又是不可或缺的,没有 compaction性能更差,而且被删除的数据还不能真正清除,HBase就提供了一个简单的处理方案:通过配置来限制compaction时 占用的IO性能
-
参数设置
hbase.regionserver.throughput.controller:你要限制的类型 对应的类名
控制合并相关指标: PressureAwareCompactionThroughputController:默认
控制刷写相关指标: PressureAwareFlushThroughputController
hbase.hstore.blockingStoreFiles:当StoreFile数量达到该 值,阻塞刷写动作
hbase.hstore.compaction.throughput.lower.bound:合并占用吞吐量下限
hbase.hstore.compaction.throughput.higher.bound:合并占用吞吐量上限
-
hbase.hstore.blockingStoreFiles
- StoreFile阻塞值,默认16,当Store中的HFile数量达到16的时候阻塞Memstore的刷写
- 所以当memstore内存急剧增加的情况下,就应该考虑这种情况,memstore达到阻塞值,被阻塞后致使不能刷写,所以memstore达到了他的写入上限
memstore写入上限:hbase.hregion.memstore.block.multiplier*hbase.hregion.memstore.flush.size
-
合并/刷写吞吐量限制机制
-
HBase会计算合并/刷写时占用的吞吐量,然后当占用吞吐量过大的 时候适当地休眠,限制机制是分高峰时段和非高峰时段的,通过两个参数配置
hbase.offpeak.start.hour:每天非高峰的起始时间,取值为 0~23的整数,包含0和23
hbase.offpeak.end.hour:每天非高峰的而结束时间,取值为 0~23的整数,包含0和23
- 在非高峰期是不限速的,只有在高峰期当合并/刷写占用了太大的 吞吐量才会休眠,决定是否要休眠是看当时占用的流量是否达到休眠吞 吐量阀值,休眠吞吐量阀值的计算公式是
lowerBound:hbase.hstore.compaction.throughput.lower.bound合并占用吞 吐量下限,默认是10 MB/sec
upperBound:hbase.hstore.compaction.throughput.higher.bound合并占用 吞吐量上限,默认是20 MB/sec
pressureRatio:压力比。限制合并时,该参数就是合并压力compactionPressure,限制刷写时该参数刷写压力 flushPressure这个数值为0~1.0
公式:lowerBound + (upperBound - lowerBound) * pressureRatio
- 当达到吞吐量阀值的时候合并线程就会sleep一段时间,为业务响 应留出足够的吞吐量,保证业务响应的流畅度和保证系统的稳定性
-
压力比
- 压力比(pressureRatio)越大,代表HFile堆积得越多,或者即将产生越多的HFile,一旦HFile达到阻塞阈值,则无法写入任何数据,系 统就不可用了.所以合并压力越大,代表着系统不可用的可能性越大
- 此时合并的需求变得迫在眉睫,我们需要分配更多的吞吐量给合并操 作。从公式中可以看出,当压力比越大的时候,吞吐量阈值就越高,意 味着合并线程可以占用更多的吞吐量来进行合并。
- 当presssureRatio的计算结果大于1.0了,说明压力太大了,在不合并集群将不能工作了,所以此时阀值是取消的,即不限制合并的吞吐量
- 压力比,分为合并压力(compactionPressure)和刷写压力 (flushPressure)两种
- compactionPressure是怎么计算出来的
(HfileCount - minFilesToCOmpact) / (blockingFileCount - minFilesToCOmpact)
HfileCount:当前storeFile数量
minFilesToCOmpact:单词合并文件数量下线,即hbase.hstore.compaction.min
blockingFileCount:即hbase.hstore.blockingStoreFiles
- 所以可以看出当前的HFile越大或者阻塞上 限越小,那么合并的压力就越大,因为更有可能发生阻塞
- 如果HFile数量比单次合并文件数量下限还小,说明绝对不会发生合并,此时CompactionPressure=0
- flushPressure是怎么计算出来的
globalMemstoreSize / memstoreLowerLimitSize
globalMemstoreSize:当前的memstore大小
memstoreLowerLimitSize:memstore刷写下线,当全局 memstore达到这个内存占用数量的时候就会开始刷写
- 从上面可以看出如果当前Memstore占用的内存越大,或者刷写的触 发条件越小,越有可能引发刷写。发生刷写后,HFile的数量就会增 多,即越有可能因为HFile过多触发阻塞
合并的时候HBase做了什么
Major Compaction
问题参考