当前位置:   article > 正文

HBase-Region的拆分与合并_第1关:hbase shell 操作:自动拆分

第1关:hbase shell 操作:自动拆分

1 Region拆分一个Region代表一个表的一段Rowkey的数据集合,当Region太大,Master会将其拆分。Region太大会导致读取效率太低,遍历时间太长,通过将大数据拆分到不同机器上,分别查询再聚合,Hbase也被人称为“一个会自动分片的数据库”。
Region可以手动和自动拆分。
1.1 Region自动拆分
1.1.1 ConstantSizeRegionSplitPolicy
固定大小拆分策略,0.94版本之前的唯一拆分方法,如果单个Region大小超过了阀值那么就拆分为两个Region,这种策略使得急群众的Region大小很平均。唯一的参数是(hbase-site.xml):

1.1.2 IncreasingToUpperBoundRegionSplitPolicy

动态限制拆分策略,是新版本的默认策略。有的数据库文件增长是翻倍的数据量,128M,256M,512M……,该策略与之类似,限制是动态的,计算公式为:

  • tableRegionCount:当前表在所有RegionServer上拥有的所有的Region数量的总和
  • initialSize:如果定义了hbase.increasing.policy.initial.size,则使用该值,否则用memstore刷写值得2倍,即hbase.hregion.memstore.flush.size*2。
  • deffaultRegionmaxFileSize:ConstantSizeRegionSplitPolicy所用到的配置项,也就是Region的最大大小
  • Math.min:取这两个数值的最小值

当初始hbase.hregion.memstore.flush.size定义为128M,过程为:

  • 刚开始只有一个Region,上限为1^31282=256M
  • 当有2个Region,上限为2^31282=2048M
  • 当有3个Region,上限为3^31282=6912M
  • 以此类推当有4个Region时候,为16G,上限达到了10GB,最大值就保持在了10G,Region数量再增加也不会增加上限

涉及到的相关配置有(hbase-site.xml中配置):

1.1.3 KeyPrefixRegionSplitPolicy
固定长度前缀拆分策略,是IncreasingToUpperBoundRegionSplitPolicy的子类,在其基础上增加了对拆分点(splitPoint,拆分点就是Region被拆分出的RwoKey)的定义,保证了有相同前缀的key不会被拆分到两个不同的Region中。该策略会根据KeyPrefixRegionSplitPolicy.prefix_lenth所定义的长度来截取rowkey作为分组的依据。
当所有数据只有一两个前缀那么keyPrefixRegionSplitPolicy就不太有效,采用默认比较好,但是前缀如果划分比较细,查询容易发生跨Region查询的情况,这时候就比较实用。所以适合实用的场景有:

  • 数据有多种前缀
  • 查询多是针对前缀,比较少跨越多个前缀来查询数据

涉及到的配置参数有:

1.1.4 DelimitedKeyPrefixRegionSplitPolicy
分隔符前缀拆分策略,也是IncreasingToUpperBoundRegionSplitPolicy的子类,按照分隔符来判断是否进行拆分,比如定义了前缀分隔符为_,那么rowkey为host1_aaaaabbbb,host2_sjfijts,这两个数据会被拆分到不同的Region中。涉及到的配置参数有:

1.1.5 BusyRegionSplitPolicy

热点拆分策略,这是唯一考虑到热点数据的拆分策略,如果数据库中的Region某些短时间内被访问很频繁,承载了很大压力,就是热点Region,涉及到的配置参数有:

上诉三个配置项都是用于计算Region是否是热点Region的配置项,计算方法为:

  • 当前时间 - 上次检测时间 >=hbase.busy.policy.aggWindow,则继续进行下一步计算
  • 这段时间被阻塞的请求/这段时间的总请求=请求的被阻塞率(aggBlockedRate)
  • 若aggBlockedRate > hbase.busy.policy.blockedRequests,那么该Region为繁忙

如果对性能要求比较高,该策略比较有效,但是因为Region拆分的不确定性,会带来很多不确定因素。
1.1.6 DisabledRegionSplitPolicy
手动拆分策略,其实就是上诉所有的策略都失效,不管如何判断,都会返回不拆分。
自动拆分策略都是数据最开始写入一个Region,之后可能会发生数据大量写入的同时还进行拆分,如果我们知道如果拆分Region,我们就可以提前定义拆分点,数据就会直接分配到各自所需的Region,手动拆分有如下良两种:
1.2 手动拆分
1.2.1 pre-splitting 
Region预拆分就是在建表的时候就定义好拆分点的算法,使用org.apache.hadoop.hbase.util.RegionSplitter类来创建表,并传入拆分点算法,就可以在建表同事定义拆分点算法。比如;

具体的拆分点算法有:
1.2.1.1 HexStringSplit
ASCII码预拆分策略,只需要传入一个要拆分的Region的数量,HexStringSplit会将数据从“00000000”到“FFFFFFFF”之间的数据长度按照n等分之后算出每一段的其实rowkey和结束rowkey,以此作为拆分点。
1.2.1.2 UniformSplit
字节码预拆分策略,与ASCII码预拆分不同的是,起始结束不是Sting而是byte[]

  • 起始rowkey是ArrayUtils.EMPTY_BYTE_ARRAY
  • 结束rowkey是new byte[]{xFF,xFF,xFF,xFF,xFF,xFF,xFF,xFF}

最后调用Bytes.split方法把其实rowkey到结束rowkey之间的长度n等分,然后取每一段的起始和结束作为拆分点
默认预拆分算法只有这两个,我们也可以通过实现SplitAlgorithm接口实现自己的拆分算法,或者干脆手动定出拆分点。
1.2.2 手动制定拆分点(属于预拆分)
只需要在建表的时候跟上SPLITS参数:

1.2.3 强制拆分

其实这个才是实际意义上的手动拆分,通过运行命令强制手动拆分(forced splits),调用hbase shell的split方法。

1.3 总结
建议开始的时候定义预拆分,导入初始数据,之后使用自动拆分来让HBase自动管理Region。不要关闭自动拆分。这样科比避免因为直接使用预拆分导致的热点Region问题。
尽量使用适合业务的拆分策略,比如不要在时间戳为rowley前缀的情况下还是iyongKeyPrefixRegionSplitPolicy来作为拆分策略,这会导致严重的热点问题。


2 Region合并(Merge)
首先,Region的合并(merge)并不是为了性能考虑而是处于维护的目的被创造出来的。比如删了大量的数据,导致每个Region都变小了,这个时候合并Region就比较合适了。
2.1 通过Merge类冷合并Region
通过org.apache.hadoop.hbase.util.Merge类来实现,不需要进入hbase shell,直接执行:
 
就可以实现两个Region的合并,但是有一个前提,必须保证这两个Region已经下线,保证HMaster和所有的HRegionServer都停掉,否则会报错,但是这样太麻烦了,而且不适合生产使用。
2.2 通过online_merge热合并Region
与冷合并不同的是,online_merge的传参是Region的hash值,而Region的hash值就是Region名称的最后那段在两个.之间的字符串部分,需要进入hbase shell:

3 HFile合并(Compact)
MemStore每次刷写都会生成一个HFile,当HFile变多,回到值读取数据磁头寻址缓慢,因为HFile都分散在不同的位置,为了防止寻址动作过多,适当的减少碎片文件,就需要合并HFile。
3.1 HFile的合并策略
合并操作主要是在一个Store里边找到需要合并的HFile,然后把它们合并起来,合并在大体意义上有两大类Minor Compation和Major Compaction:

  • Minor Compaction:将Store中多个HFile合并为一个HFile,这个过程中,达到TTL(记录保留时间)会被移除,但是有墓碑标记的记录不会被移除,因为墓碑标记可能存储在不同HFile中,合并可能会跨国部分墓碑标记。这种合并的触发频率很高
  • Major Compaction:合并Store中所有的HFile为一个HFile(并不是把一个Region中的HFile合并为一个),这个过程有墓碑标记的几率会被真正移除,同时超过单元格maxVersion的版本记录也会被删除。合并频率比较低,默认7天执行一次,并且性能消耗非常大,最后手动控制进行合并,防止出现在业务高峰期。

需要注意的是,有资料说只有Major合并才会删数据,其实Major合并删除的是带墓碑标记的,而Minor合并直接就不读取TTL过期文件,所以也相当于删除了。
3.1.1 老版本的合并策略(0.96之前)
RationBasedCompactionPolicy
基于固定因子的合并策略,从旧到新扫描HFile文件,当扫描到某个文件满足条件:

就把该HFile和比它更新的所有HFile合并为一个HFile。
但是实际上,Memstore很多情况下会有不同的刷写情况,所以每次的HFile不一定一样大,比如最后一次导入数据需要关闭Region,强制刷写导致数据非常少。实际情况下RatioBasedCompactionPolicy算法效果很差,经常应发大面积合并,合并就不能写入数据,影响IO。
3.1.2 新版本合并策略 
ExploringCompactionPolicy
是新版本的默认算法,不再是强顺序遍历,而是整体遍历一遍然后综合考虑,算法模型是:

如果HFile小于minCompactionSize,则不需要套用公式,直接进入待合并列表,如果没有配置该项,那么使用hbase.hregion.memstore.flush.size。

所以一个HFile是否进行参与合并:

  • 小于minCompactionSize,直接进入合并列表
  • 大minCompactionSize,用公式决定是否参与合并
  • 穷举出所有可执行合并列表,要求数量要>hbase.hstore.compaction.min.size,且<=hbase.hstore.compaction.max.size
  • 比较所有组,选出其中数量最多
  • 在数量最多且一样的组中,选择总大小最小的一组

3.1.3 FIFOCompactionPolicy
先进先出合并策略,最简单的合并算法,甚至可以说是一种删除策略。minor、major合并一定会发生但是频率不同,但是有时候没必要执行合并:

  • TTL特别短,比如中间表,只是在计算中间暂存一些数据,很容易出现一整个HFile都过去
  • BlockCache非常大,可以把整个RegionServer上的数据都放进去,就没必要合并了,因为所有数据都可以走缓存

因为合并会把TTL超时数据删掉,两个合并就回导致其实把HFile都删除了,而FIFO策略在合并时候就会跳过含有未过期数据的HFile,直接删除所有单元格都过期的块,所以结果就是过期的快上整个被删除,而没过期的完全没有操作。而过程对CPU、IO完全没有压力。但是这个策略不能用于:

  • 表没有设置TTL,或者TTL=ForEver
  • 表设置了Min_Versions,并且大于0

因为如果设置了min_versions,那么TTL就会失效,芮然达到了TTL时间,但是因为有最小版本数的限制,依旧不能删除。但是如果手动进行delete依旧可以删到小于最小版本数,min_versions只能约束TTL,所以FIFO不适用设置了min_versions的情况。
3.1.4 DateTieredCompactionPolicy
日期迭代合并删除策略,主要考虑了一个比较重要的点,最新的数据可能被读取的次数是最多的,比如电商中用户总是会查看最近的订单;随着时间的推移,用户会逐渐减缓对老数据的点击;非常古老的记录鲜有人问津。
电商订单这种数据作为历史记录一定没有TTL,那么FIFO合并策略不合适;单纯的看大小进行合并也不太有效,如果把新数据和老数据合并了,反而不好,所以Exploring也不友好。Ratio就更别考虑了,每次合并都卷入最新的HFile。
所以DateTieredCompactionPolicy的特点是:

  • 为了新数据的读取性能,将新旧文件分开处理,新的和新的合并,旧的和旧的合并
  • 不能只把数据分为新旧两种,因为读取频率是连续的,所以可以分为多个阶段,分的越细越好
  • 太早的文件就不合并了,没什么人读取,合不合并没有太大差别,合并了还消耗性能

涉及到的配置项有:

有一个确定就是,如果Store中的某个HFile太老了,但是有没有超过TTL,且大于了最老层次时间,那么这个HFile在超时被删除之前都不会被删除,不会发生Major合并,用户手动删除数据也不会真正被删除,而是一直占用着空间。
其实归根到底,合并的最终策略是每个时间涌口中的HFile数量达到了最小合并数量,那么就回进行合并。另外,当一个HFile跨了时间线的时候,将其定义为下一个时间窗口(更老的更长的)。
适用场景:

  • 适用于经常读写最近数据的系统,专注于新数据
  • 因为哪个缺点的原因可能引发不了Major合并,没法删除手动删除的信息,更适合基本不删数据的系统
  • 根据时间排序的存储也适合
  • 如果数据修改频率较低,只修改最近的数据,也推荐使用
  • 如果数据改动平凡,老数据也修改,经常边读边写数据,那么就不太适合了。

3.1.5 StripeCompactionPolicy
简单来说就是将一个Store里边的数据分为多层,数据从Memstore刷写到HFile先落到level 0,当level 0大小超过一定的阀值时候会引发一次合并,会将level 0读取出来插入到level 1的HFile中去,而level 1的块是根据建委范围划分的,最早是分为多层的,后来感觉太复杂,将level 0改名为L0,而level 1-N合并成一个层叫做strips层。依旧是按照键位来划分块。
这种策略的好处是:

  • 通过增加L0层,给合并操作增加了一层缓冲,让合并操作更加缓和;
  • 严格按照键位来划分Strips,碎玉读取虽然不能提高太多速度,但是可以提高查询速度的稳定性,执行scan时候,跨越的HFile数量保持在了一个比较稳定的数值
  • Major合并本来是牵涉到一个store中的所有HFile,现在可以按照子Strip执行了,因为Major合并一直都会存在因为牵涉HFile太多导致的IO不稳定,而该策略一次只是牵涉到一个Strip中的文件,所以克服了IO不稳定的缺点

Stripe合并策略对于读取的优化要好于写的优化,所以很难说会提高多少IO性能,最大的好处就是稳定。那么什么场景适合StripeCompactionPolicy呢?

  • Region要足够大,如果Region小于2GB那么就不适合该策略,因为小Region用strips会细分为多个stripe,反而增加IO负担
  • RowKey要具有统一的格式,能够均匀分布,如果使用timestamp来做rowkey,那么数据就没法均匀分布了,而用26个首字母就比较合适

3.2 HFile合并的吞吐量限制参数
在使用过程中,如果突然发生IO降低,十有八九是发生compaction了,但是compaction又是不可获取的,所以就可以通过限制compaction的吞吐量来限制其占用的性能。
由于HBase是分布式系统,吞吐量的概念是磁盘IO+网络IO的笼统概念,因为没办法具体判断哪一个的IO的限制更大,HBase的吞吐量是通过要合并的HFile的文件大小/处理时间得出的。未发生合并之前就没法测了,只能通过上一次的合并信息进行简单预测。具体的设置参数有;

hbase.hstore.blockingStoreFile的设置比较讲究,如果设置不合理,当HFile数量达到该值之后,会导致Memstore占用的内存急剧上升,很快就达到了Memstore写入上限,导致memstore阻塞,一点都写不进去。所以Memstore达到阻塞值的时候,先不要急着调大Memstore的阻塞阀值,要综合考虑HFile的合并阻塞值,可以适当调大,20、30、50都不算多,HFile多,只是读取性能下降而已,但是达到阻塞值不只是慢的问题了,是直接写不进去了。
3.2.1 合并/刷写吞吐量限制机制HBase会将合并和刷写总的吞吐量做计算,如果总吞吐量太大,那么进行适当休眠,因为这两个参数会限制合并时候占用的吞吐量,也会限制刷写时候占用的吞吐量。保证业务的响应流畅、保障系统的稳定性。限制会区分高峰时段和非高峰时段,通过如下两个参数:

通过设置非高峰,其他时段就是高峰时段了。在非高峰期是不会进行限速,只有在高峰期占用了太大的吞吐量才会休眠,主要看一个阀值:

3.2.2 压力比压力比(pressureRatio)越大,代表HFile堆积的越多,或者即将产生越多的HFile,一旦达到HFile的阻塞阀值,那么久无法写入数据了,所以合并压力比越大,合并的需求变得迫在眉睫。压力比越大,吞吐量的阀值越高,意味着合并线程可以占用更多的吞吐量来进行合并。
压力比有两种:
合并压力(compactionPressure)

当前的StoreFile越大,或者阻塞上限越小,合并压力越大,更可能发生阻塞
刷写压力(flusthPressure)

如果当前的Memstore占用内存越大,或者触发条件越小,越有可能引发刷写,刷写后HFile增多,就有可能发生HFile过多阻塞。
3.3合并的具体过程具体步骤为:

  • 获取需要合并的HFile列表
  • 由列表创建出StoreFileScanner
  • 把数据从这些HFile中读出,并放到tmp目录(临时文件夹)
  • 用合并后的新HFile来替换合并前的那些HFile

3.2.1 minor Compaction第一步:获取需要合并的HFile列表
获取列表的时候需要排除掉带锁的HFile,分为写锁(write lock)和读锁(read lock),当HFile正在进行如下操作时候会进行上锁:

  • 用户正在进行scan查询->上Region读锁(region read lock)
  • Region正在切分(split):此时Region会先关闭,然后上Region写锁(region write lock)
  • Region关闭->上region写锁(region write lock)
  • Region批量导入->Region写锁(region write lock)

第二步:由列表创建出StoreFileScanner
HRegion会创建出一个Scanner,用Scanner读取所有需要合并的HFile上的数据
第三步:把数据从这些HFile中读出,并放到tmp(临时目录)
HBase会在临时目录中创建新的HFile,并把Scanner读取到的数据放入新的HFile,数据过期(达到了TTL时间)不会被读出:
第四步:用合并后的HFile来替换合并前的那些HFile
最后用哪个临时文件夹合并后的新HFile来替换掉之前那些HFile文件,过期的数据由于没有被读取出来,所以就永远消失了。
3.2.2 Major CompactionHBase中并没有一种合并策略叫做Major Compaction,Major Compaction目的是增加读性能,在Minor Compaction的基础上可以实现删除掉带墓碑标记的数据。因为有时候,用户删除的数据,墓碑标记和原始数据这两个KeyValue在不同的HFile上。在Scanner阶段,会将所有HFile数据查看一遍,如果数据有墓碑标记,那么就直接不Scan这条数据。
所以之前介绍的每一种合并策略,都有可能升级变为majorCompaction,如果本次Minor Compaction包含了当前Store所有的HFile,并且达到了足够的时间间隔,则会被升级为Major Compaction。这两个阀值的配置项为:

  • hbase.hregion.majorcompaction:Major Compaction发生的周期,单位毫秒,默认7天
  • hbase.hregion.majorcompaction.jitter:Major Compaction周期抖动参数,0-1.0,让MajorCompaction发生时间更加灵活,默认0.5

建议关闭自动Major Compaction,将base.hregion.majorcompaction设置为0.但也不要完全不进行Major Compaction,可以定义一些定时任务在非业务高峰期进行手动调用M按金融Compaction。
3.4 总结详细查看各种策略的合适场景,根据场景做策略选择

  • 如果数据有固定TTL,并且新数据容易被督导,那么选择DataTieredCompaction
  • 如果没有TTL或者TTL较大,选择StripeCompaction
  • FIFOCompaction一般不会被用到,在一些极端情况,比如生存时间特别短的数据。如果想用FIFO,可以先测试一下DataTieredCompaction的性能。



本文转载自:
链接:https://www.jianshu.com/p/7359a1789d24

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/522209
推荐阅读
相关标签
  

闽ICP备14008679号