今天在使用datax同步数据到hbase的时候,随着同步的数据越来越多,发现同步的速度越来越慢,且慢慢的出现同步数据为0的情况,以及regionserver间歇性挂掉,最后完全挂掉了。
首先说一下,使用的hbase是单节点的,自己用来测试的。没有做过多的配置。
hbase的表只设置了一个列族,列数不到30列。机器内存16G。
当数据写到500万之后,开始变慢。最后写到800万regionserver崩溃。
2020-05-25 10:31:53,453 INFO [RpcServer.default.FPBQ.Fifo.handler=29,queue=2,port=16020] regionserver.HRegion: writing data to region trans014744559352592,1590373894580.120ac4998d1fe3f32ca7aa15b39cc231. with WAL disabled. Data may be lost in the event of a crash.
2020-05-25 10:32:01,013 WARN [MemStoreFlusher.1] regionserver.MemStoreFlusher: 120ac4998d1fe3f32ca7aa15b39cc231 has too many store files(17000 ms
2020-05-25 10:32:02,705 INFO [JvmPauseMonitor] util.JvmPauseMonitor: Detected pause in JVM or host machine (eg GC): pause of approximately
GC pool 'ParNew' had collection(s): count=1 time=43ms
2020-05-25 10:33:58,204 WARN [regionserver/xxx:16020] compactions.CompactionProgress: totalCompactingKVs=10657607 less tha3712
2020-05-25 10:34:01,230 WARN [regionserver/xxx:16020] compactions.CompactionProgress: totalCompactingKVs=10657607 less tha7289
2020-05-25 10:34:04,388 WARN [regionserver/xxx:16020] compactions.CompactionProgress: totalCompactingKVs=10657607 less tha2330
2020-05-25 10:34:07,689 WARN [regionserver/xxx:16020] compactions.CompactionProgress: totalCompactingKVs=10657607 less tha7035
看资料,说是flush,导致store file数量过多,来不及compact,进而导致写阻塞。。。
之前对这几个原理有一些模棱两可,现在整理一下。
flush刷写
如果一个region有n个列族,那么就对应有n个store,每个store都有一个memstore和若干个storefile(memstore每flush一次就生成一个storefile持久化到hdfs磁盘)。
默认情况下,memstore有三种触发flush的机制:
- 刷写的最小单元是region,一个region中如果某个memstore占用的内存达到刷写阙值hbase.hregion.memstore.flush.size(默认128M)时就会触发此region下所有memstore的刷写。
- 除此之外,当一个regionserver下所有的memstore占用的内存总和达到hbase.regionserver.global.memstore.upperLimit设定的值时,会触发当前regionserver下所有region的所有memstore刷写。刷写顺序按照各个region的MemStore的大小降序刷写的,就是先刷写memstore大的region。直到全局的memstore低于hbase.regionserver.global.memstore.lowerLimit时,结束刷写。
- 或者是当一个regionserver的预写日志WAL的数量达到hbase.regionserver.max.logs指定的值时,也会触发当前regionserver下所有region的所有memstore刷写。这种方式的刷写顺序是按照时间升序进行的,也就是memstore越老的region,先刷写,直到WAL数量小于hbase.regionserver.max.logs时结束刷写。
根据第一条flush机制可知,如果没有预分区,且只有一个列族的时候,所有的数据都会往一个memstore中写,导致memstore迅速增大,导致频繁flush。
compact合并
随着数据的不断增加,一个store下的storefile会越来越多。为了提高读性能,需要将众多的小storefile进行compact合并。但是compact是资源密集型的操作,可以在很多方面影响性能(可能提高性能,也可能降低性能)。
compact的触发条件也有多个,其中一个是当storefile数量至少达到3个时才可以进行一次小合并。
因此,在频繁flush时,必然对应频繁的compact。
当写请求非常多,导致不断生成HFile,但compact的速度远远跟不上HFile生成的速度,这样就会使HFile的数量会越来越多,导致读性能急剧下降。为了避免这种情况,在HFile的数量过多的时候会限制写请求的速度:在每次执行MemStore flush的操作前,如果HStore的HFile数超过hbase.hstore.blockingStoreFiles (默认7),则会阻塞flush操作hbase.hstore.blockingWaitTime时间,在这段时间内,如果compact操作使得HStore文件数下降到回这个值,则停止阻塞。另外阻塞超过时间后,也会恢复执行flush操作。这样做就可以有效地控制大量写请求的速度,但同时这也是影响写请求速度的主要原因之一。
参考:
https://blog.csdn.net/zyc88888/article/details/79666846
split切分
当一个region下的storefile达到一定阙值时,就会对此region进行split切分成两个region。
split是为了对region进行负载均衡到多个regionserver。
split的触发机制有多种,2.x版本采用的切分策略是SteppingSplitPolicy
SteppingSplitPolicy策略
当region的最大一个store达到一个阈值时触发split,不过这个阈值不是固定的,而是根据regionServer管理的同属一张表的region个数有关:
- region个数为1时,maxFilesize = flushSize * 2
- region个数为0或者大于100:maxFilesize的选取为hbase.hregion.max.filesize设置的值(默认10G)
- region个数在2~100之间:有一个initialSize,这个值由hbase.incresing.policy.initial.size配置;若没有配置这个值,则initialSize = 2 * MEMSTORE_FLUSHSIZE(table元数据);若table没有元数据或initialSize小于0,则initialSize = 2 * hbase.hregion.memstore.flush.size(默认128M)。拿到initialSize后,maxFilesize = min (initialSize * region个数的3次方, 10G)。
根据默认的split策略规则可以看到,如果没有设置预分区,那么一开始region的数量从1开始split,且会频繁split(因为maxFilesize = min (initialSize * region个数的3次方, 10G))。
具体可以看下面的第二个参考博客。
参考:
https://www.jianshu.com/p/53459997c814
https://www.cnblogs.com/cnblogs-syui/p/12566642.html
通过重新理解上面几个概念,可以大概知道,出现这个报错是因为持续往一个region写数据,导致频繁flush,频繁compact。而compact是比较重的操作,会写阻塞,同时compact是将待合并的hfile锁定后复制合并成一个新的hfile,然后删除旧的。所以这个过程会有大量的磁盘io,十分影响性能。
解决办法是预分区,避免大量的数据都在一个region下,导致compact的数据量过大,影响性能。
具体优化参数可以参考上述博客。