一次Hbase-RegionServer连续宕机事故

背景:

线上通过webui监控发现,几乎所有的 regionserver 都宕机了,而且时间上是连续的。通过查看所有regionserver上的日志发现,有过 gc 时间超长的(20多秒),也有 zk 连接超时的,还有在 major compaction 时宕机的。到这就很为难了。

措施:

查看HBase现在状态以及配置,看看配置是否合理

发现整个集群30台左右rs,每台机器分配了 20G 内存,写缓存比是 0.4,读缓存比是 0.4,region大小是10g,major compact 是 7天自动执行,最大WAL 数未配置,即默认为32(32是一个策略算出来的,未配置就通过默认的策略算), 整个集群所有region数大概在10000 左右。

按照这种配置,每天集群写缓存只有8G。然后每台机器有大约300个region,活跃在用的region大概有270左右。也就是说每个region的 memstore 大小达到3M左右就需要flush,加上写入并发在十几万,推断是因为flush太过频繁,导致 minor compact 频繁。另外,region 大小只有10G,很容易达到分裂要求,也是对集群稳定性的一大考验。

初步解决方案:修改配置,重启集群。

​ 读写缓存比调整,因为写需求大于读需求,因此读写分别调整为 0.3 和 0.5(两者之和不能超过0.8);

​ region 大小调整,10G 调整为 100G,降低 region 分裂频率;

​ 禁止自动 major compact,设置为0即禁止。major 操作会使RS几乎不可用,禁止自动,需要周期手动

​ 修改 maxLogs 大小,因为 region 数太多,WAL数量修改为64或128 (这次好像未改动)

修改完后进行重启。

该配置只是将未来的做了打算,比如 region不会再分裂,不会再自动 major ,等等,但是现有的问题还是存在,region数太多,导致 memStore 经常flush,RS压力还是很大。故还需要先进行region 合并操作,先将region数量减少一般再说。

合并操作代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package hbase;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class MergeRegions {

public static void main(String[] args) throws Exception{

Configuration configuration = HBaseConfiguration.create();
configuration.set("hbase.zookeeper.property.clientPort", "2181");
configuration.set("hbase.rootdir", "hdfs://hadoopha:8020/apps/hbase/data");
configuration.set("zookeeper.znode.parent", "/hbase-unsecure");
configuration.set("hbase.zookeeper.quorum","node31,node32,node33");

Connection connection = ConnectionFactory.createConnection(configuration);
Admin admin = connection.getAdmin();

List<HRegionInfo> regions = admin.getTableRegions(TableName.valueOf("oracledb.bdp_hbase_kvbehavior_jydz"));

// 因为只有相邻 rowKey 的region 才能进行合并,所以需要先根据 每个region的startKey进行排序
Collections.sort(regions, new Comparator<HRegionInfo>() {
@Override
public int compare(HRegionInfo o1, HRegionInfo o2) {
return Bytes.compareTo(o1.getStartKey(),o1.getStartKey());
}
});

HRegionInfo hRegionInfo = null;
for (HRegionInfo r:regions){
int i = regions.indexOf(r);
// 每次合并两个相邻的 region
if (i % 2 == 0){
hRegionInfo = r;
}
else {
System.out.println("start merge 2 regions... "+i+ ": "+hRegionInfo.getEncodedName()+" " +Bytes.toString(hRegionInfo.getStartKey())+" and "+ (i+1) +": "+ r.getEncodedName()+" "+Bytes.toString(r.getStartKey()));
admin.mergeRegions(hRegionInfo.getEncodedNameAsBytes(),r.getEncodedNameAsBytes(),false);
}
System.out.println("all regions merge success!");
}
}
}

合并后检查 region数,降低了一半。

然后重启写入任务,发现了另外一个问题,有一个region A 处于分裂状态,但是有数据需要写到那个region,然后异常了,一直写不进去,异常信息大概是定位到了写入的region,但是region正在分裂,需要等待…,谁知道等待了一天也不行,后面仔细想了想,正常的话,region的split应该是一个一两秒的过程,不至于会出现这个问题,那么这个region肯定不正常。后面去 hbase:meta表查看了一下该region的元数据信息,反正确实一直处于split 状态,并且两个子region的信息也有。然后分别去HDFS上查看父region和两个子region的数据信息,发现其中一个子region目录下还有父region的数据链接文件,另外一个region目录信息正常,但是元数据查不到该子region的信息,万万没想到这个坑了我一下(下线的region的元数据信息不会在meta表)。所以第一个神操作来了,把无元数据信息的region的目录删除。我以为该region已经没用了,所以执行 hbase hbck能检查出问题,没想到的是,问题出来了,不是父region的问题,是这个子region已经分裂,但是它分裂的子region还有数据链接文件(即数据未迁移),然后就报错了。接着赶紧把 子region 恢复,然后基本判断了。是另外一个子region子出了问题,但是其下又有新的数据文件,所以将 父region下的数据文件全部手动迁移至该region下,并且将对应的数据链接文件进行删除。然后再执行 hbck ,问题解决,写入异常也没了。

接着再看 写入不稳定问题,几乎每个十来分钟,消费写入程序都会进行 rebalance,苦不堪言。然后大概评估了一下原因,还是region太多,memstore不够用。但是再次合并发现,不能再合并了,想了想,合并和分裂应该是相反的操作,也不会立马迁移数据,需要时间。在这期间应该时不能再合并了(好像执行了major 也不管用,也不会立马迁移数据)。那么只能从kafka consumer端解决了。看代码发现,max.poll.interval.ms 这个参数仍然是默认的 300 s。看来就是这个了,因为写HBase会经常卡住一会,然后就把 这个值设置为 1小时了。通常情况下不建议设置这么大,都需要参考处理每批 poll 数据的时间来进行设置,稍长一点即可。但是因为现在经常抖动,也不知道具体的处理时间,因此直接设置为1小时。后果就是再次发生rebanlance时比较难办,因为rebanlance的超时时间其实好像也是这个值,所以,这个值设置千万要慎重。

当改为这一切之后,终于算是正常了。差不多没隔十来分钟左右会停止写入数据,然后程序等待,等待2-5分钟,恢复写入。最起码不会因为rebanlance而导致程序一直 rebanlance 了。