HBase Region 合并分析

栏目: 数据库 · 发布时间: 6年前

内容简介:HBase中表的基本单位是Region,日常在调用HBase API操作一个表时,交互的数据也会以Region的形式进行呈现。一个表可以有若干个Region,今天笔者就来和大家分享一下Region合并的一些问题和解决方法。在分析合并Region之前,我们先来了解一下Region的体系结构,如下图所示:

1.概述

HBase中表的基本单位是Region,日常在调用HBase API操作一个表时,交互的数据也会以Region的形式进行呈现。一个表可以有若干个Region,今天笔者就来和大家分享一下Region合并的一些问题和解决方法。

2.内容

在分析合并Region之前,我们先来了解一下Region的体系结构,如下图所示:

HBase Region 合并分析

从图中可知,能够总结以下知识点:

  • HRegion:一个Region可以包含多个Store;

  • Store:每个Store包含一个Memstore和若干个StoreFile;

  • StoreFile:表数据真实存储的地方,HFile是表数据在HDFS上的文件格式。

如果要查看HFile文件,HBase有提供命令,命令如下:

1hbase hfile -p -f /hbase/data/default/ip_login/d0d7d881bb802592c09d305e47ae70a5/_d/7ec738167e9f4d4386316e5e702c8d3d

执行输出结果,如下图所示:

HBase Region 合并分析

2.1 为什么需要合并Region

那为什么需要合并Region呢?这个需要从Region的Split来说。当一个Region被不断的写数据,达到Region的Split的阀值时(由属性hbase.hregion.max.filesize来决定,默认是10GB),该Region就会被Split成2个新的Region。随着业务数据量的不断增加,Region不断的执行Split,那么Region的个数也会越来越多。

一个业务表的Region越多,在进行读写操作时,或是对该表执行Compaction操作时,此时集群的压力是很大的。这里笔者做过一个线上统计,在一个业务表的Region个数达到9000+时,每次对该表进行Compaction操作时,集群的负载便会加重。而间接的也会影响应用程序的读写,一个表的Region过大,势必整个集群的Region个数也会增加,负载均衡后,每个RegionServer承担的Region个数也会增加。

因此,这种情况是很有必要的进行Region合并的。比如,当前Region进行Split的阀值设置为30GB,那么我们可以对小于等于10GB的Region进行一次合并,减少每个业务表的Region,从而降低整个集群的Region,减缓每个RegionServer上的Region压力。

2.2 如何进行Region合并

那么我们如何进行Region合并呢?HBase有提供一个合并Region的命令,具体操作如下:

1# 合并相邻的两个Region
2hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME'
3# 强制合并两个Region
4hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', true

但是,这种方式会有一个问题,就是只能一次合并2个Region,如果这里有几千个Region需要合并,这种方式是不可取的。

2.2.1 批量合并

这里有一种批量合并的方式,就是通过编写脚本(merge_small_regions.rb)来实现,实现代码如下:

  1# Test Mode:
  2#
  3# hbase org.jruby.Main merge_empty_regions.rb namespace.tablename <skip_size> <batch_regions> <merge?>
  4#
  5# Non Test - ie actually do the merge:
  6#
  7# hbase org.jruby.Main merge_empty_regions.rb namespace.tablename <skip_size> <batch_regions> merge
  8#
  9# Note: Please replace namespace.tablename with your namespace and table, eg NS1.MyTable. This value is case sensitive.
 10
 11require 'digest'
 12require 'java'
 13java_import org.apache.hadoop.hbase.HBaseConfiguration
 14java_import org.apache.hadoop.hbase.client.HBaseAdmin
 15java_import org.apache.hadoop.hbase.TableName
 16java_import org.apache.hadoop.hbase.HRegionInfo;
 17java_import org.apache.hadoop.hbase.client.Connection
 18java_import org.apache.hadoop.hbase.client.ConnectionFactory
 19java_import org.apache.hadoop.hbase.client.Table
 20java_import org.apache.hadoop.hbase.util.Bytes
 21
 22def list_bigger_regions(admin, table, low_size)
 23  cluster_status = admin.getClusterStatus()
 24  master = cluster_status.getMaster()
 25  biggers = []
 26  cluster_status.getServers.each do |s|
 27    cluster_status.getLoad(s).getRegionsLoad.each do |r|
 28      # getRegionsLoad returns an array of arrays, where each array
 29      # is 2 elements
 30
 31      # Filter out any regions that don't match the requested
 32      # tablename
 33      next unless r[1].get_name_as_string =~ /#{table}\,/
 34      if r[1].getStorefileSizeMB() > low_size
 35        if r[1].get_name_as_string =~ /\.([^\.]+)\.$/
 36          biggers.push $1
 37        else
 38          raise "Failed to get the encoded name for #{r[1].get_name_as_string}"
 39        end
 40      end
 41    end
 42  end
 43  biggers
 44end
 45
 46# Handle command line parameters
 47table_name = ARGV[0]
 48low_size = 1024
 49if ARGV[1].to_i >= low_size
 50  low_size=ARGV[1].to_i
 51end
 52
 53limit_batch = 1000
 54if ARGV[2].to_i <= limit_batch
 55  limit_batch = ARGV[2].to_i
 56end
 57do_merge = false
 58if ARGV[3] == 'merge'
 59  do_merge = true
 60end
 61
 62config = HBaseConfiguration.create();
 63connection = ConnectionFactory.createConnection(config);
 64admin = HBaseAdmin.new(connection);
 65
 66bigger_regions = list_bigger_regions(admin, table_name, low_size)
 67regions = admin.getTableRegions(Bytes.toBytes(table_name));
 68
 69puts "Total Table Regions: #{regions.length}"
 70puts "Total bigger regions: #{bigger_regions.length}"
 71
 72filtered_regions = regions.reject do |r|
 73  bigger_regions.include?(r.get_encoded_name)
 74end
 75
 76puts "Total regions to consider for Merge: #{filtered_regions.length}"
 77
 78filtered_regions_limit = filtered_regions
 79
 80if filtered_regions.length < 2
 81  puts "There are not enough regions to merge"
 82  filtered_regions_limit = filtered_regions
 83end
 84
 85if filtered_regions.length > limit_batch
 86   filtered_regions_limit = filtered_regions[0,limit_batch]
 87   puts "But we will merge : #{filtered_regions_limit.length} regions because limit in parameter!"
 88end
 89
 90
 91r1, r2 = nil
 92filtered_regions_limit.each do |r|
 93  if r1.nil?
 94    r1 = r
 95    next
 96  end
 97  if r2.nil?
 98    r2 = r
 99  end
100  # Skip any region that is a split region
101  if r1.is_split()
102    r1 = r2
103    r2 = nil
104  puts "Skip #{r1.get_encoded_name} bcause it in spliting!"
105    next
106  end
107  if r2.is_split()
108    r2 = nil
109 puts "Skip #{r2.get_encoded_name} bcause it in spliting!"
110    next
111  end
112  if HRegionInfo.are_adjacent(r1, r2)
113    # only merge regions that are adjacent
114    puts "#{r1.get_encoded_name} is adjacent to #{r2.get_encoded_name}"
115    if do_merge
116      admin.mergeRegions(r1.getEncodedNameAsBytes, r2.getEncodedNameAsBytes, false)
117      puts "Successfully Merged #{r1.get_encoded_name} with #{r2.get_encoded_name}"
118      sleep 2
119    end
120    r1, r2 = nil
121  else
122    puts "Regions are not adjacent, so drop the first one and with the #{r2.get_encoded_name} to  iterate again"
123    r1 = r2
124    r2 = nil
125  end
126end
127admin.close

该脚本默认是合并1GB以内的Region,个数为1000个。如果我们要合并小于10GB,个数在4000以内,脚本(merging-region.sh)如下:

 1#! /bin/bash
 2
 3num=$1
 4
 5echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : RegionServer Start Merging..."
 6if [ ! -n "$num" ]; then
 7    echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : Default Merging 10 Times."
 8    num=10
 9elif [[ $num == *[!0-9]* ]]; then
10    echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : Input [$num] Times Must Be Number."
11    exit 1
12else
13    echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : User-Defined Merging [$num] Times."
14fi
15
16for (( i=1; i<=$num; i++ ))
17do
18    echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : Merging [$i] Times,Total [$num] Times."
19    hbase org.jruby.Main merge_small_regions.rb namespace.tablename 10240  4000 merge
20    sleep 5
21done

在merging-region.sh脚本中,做了参数控制,可以循环来执行批量合并脚本。可能在实际操作过程中,批量执行一次Region合并,合并后的结果Region还是有很多(可能此时又有新的Region生成),这是我们可以使用merging-region.sh这个脚本多次执行批量合并Region操作,具体操作命令如下:

1# 默认循环10次,例如本次循环执行5次
2sh merging-region.sh 5

2.3 如果在合并Region的过程中出现永久RIT怎么办

在合并Region的过程中出现永久RIT怎么办?笔者在生产环境中就遇到过这种情况,在批量合并Region的过程中,出现了永久MERGING_NEW的情况,虽然这种情况不会影响现有集群的正常的服务能力,但是如果集群有某个节点发生重启,那么可能此时该RegionServer上的Region是没法均衡的。因为在RIT状态时,HBase是不会执行Region负载均衡的,即使手动执行balancer命令也是无效的。

如果不解决这种RIT情况,那么后续有HBase节点相继重启,这样会导致整个集群的Region验证不均衡,这是很致命的,对集群的性能将会影响很大。经过查询HBase JIRA单,发现这种MERGING_NEW永久RIT的情况是触发了HBASE-17682的BUG,需要打上该Patch来修复这个BUG,其实就是HBase源代码在判断业务逻辑时,没有对MERGING_NEW这种状态进行判断,直接进入到else流程中了。源代码如下:

 1for (RegionState state : regionsInTransition.values()) {
 2        HRegionInfo hri = state.getRegion();
 3        if (assignedRegions.contains(hri)) {
 4          // Region is open on this region server, but in transition.
 5          // This region must be moving away from this server, or splitting/merging.
 6          // SSH will handle it, either skip assigning, or re-assign.
 7          LOG.info("Transitioning " + state + " will be handled by ServerCrashProcedure for " + sn);
 8        } else if (sn.equals(state.getServerName())) {
 9          // Region is in transition on this region server, and this
10          // region is not open on this server. So the region must be
11          // moving to this server from another one (i.e. opening or
12          // pending open on this server, was open on another one.
13          // Offline state is also kind of pending open if the region is in
14          // transition. The region could be in failed_close state too if we have
15          // tried several times to open it while this region server is not reachable)
16          if (state.isPendingOpenOrOpening() || state.isFailedClose() || state.isOffline()) {
17            LOG.info("Found region in " + state +
18              " to be reassigned by ServerCrashProcedure for " + sn);
19            rits.add(hri);
20          } else if(state.isSplittingNew()) {
21            regionsToCleanIfNoMetaEntry.add(state.getRegion());
22          } else {
23            LOG.warn("THIS SHOULD NOT HAPPEN: unexpected " + state);
24          }
25        }
26      }

修复之后的代码如下:

 1for (RegionState state : regionsInTransition.values()) {
 2        HRegionInfo hri = state.getRegion();
 3        if (assignedRegions.contains(hri)) {
 4          // Region is open on this region server, but in transition.
 5          // This region must be moving away from this server, or splitting/merging.
 6          // SSH will handle it, either skip assigning, or re-assign.
 7          LOG.info("Transitioning " + state + " will be handled by ServerCrashProcedure for " + sn);
 8        } else if (sn.equals(state.getServerName())) {
 9          // Region is in transition on this region server, and this
10          // region is not open on this server. So the region must be
11          // moving to this server from another one (i.e. opening or
12          // pending open on this server, was open on another one.
13          // Offline state is also kind of pending open if the region is in
14          // transition. The region could be in failed_close state too if we have
15          // tried several times to open it while this region server is not reachable)
16          if (state.isPendingOpenOrOpening() || state.isFailedClose() || state.isOffline()) {
17            LOG.info("Found region in " + state +
18              " to be reassigned by ServerCrashProcedure for " + sn);
19            rits.add(hri);
20          } else if(state.isSplittingNew()) {
21            regionsToCleanIfNoMetaEntry.add(state.getRegion());
22          } else if (isOneOfStates(state, State.SPLITTING_NEW, State.MERGING_NEW)) {
23             regionsToCleanIfNoMetaEntry.add(state.getRegion());
24           }else {
25            LOG.warn("THIS SHOULD NOT HAPPEN: unexpected " + state);
26          }
27        }
28      }

但是,这里有一个问题,目前该JIRA单只是说了需要去修复BUG,打Patch。但是,实际生产情况下,面对这种RIT情况,是不可能长时间停止集群,影响应用程序读写的。那么,有没有临时的解决办法,先临时解决当前的MERGING_NEW这种永久RIT,之后在进行HBase版本升级操作。

办法是有的,在分析了MERGE合并的流程之后,发现HBase在执行Region合并时,会先生成一个初始状态的MERGING_NEW。整个Region合并流程如下:

HBase Region 合并分析

从流程图中可以看到,MERGING_NEW是一个初始化状态,在Master的内存中,而处于Backup状态的Master内存中是没有这个新Region的MERGING_NEW状态的,那么可以通过对HBase的Master进行一个主备切换,来临时消除这个永久RIT状态。而HBase是一个高可用的集群,进行主备切换时对用户应用来说是无感操作。因此,面对MERGING_NEW状态的永久RIT可以使用对HBase进行主备切换的方式来做一个临时处理方案。之后,我们在对HBase进行修复BUG,打Patch进行版本升级。

3.总结

HBase的RIT问题,是一个比较常见的问题,在遇到这种问题时,可以先冷静的分析原因,例如查看Master的日志、仔细阅读HBase Web页面RIT异常的描述、使用hbck命令查看Region、使用fsck查看HDFS的block等。分析出具体的原因后,我们在对症下药,做到大胆猜想,小心求证。

4.结束语

HBase Region 合并分析

长按关注,获取更多干货

这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!

博主出版了《Hadoop大数据挖掘从入门到进阶实战》,喜欢的朋友或同学, 可以去购买博主的书进行学习,在此感谢大家的支持。

HBase Region 合并分析


以上所述就是小编给大家介绍的《HBase Region 合并分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Rework

Rework

Jason Fried、David Heinemeier Hansson / Crown Business / 2010-3-9 / USD 22.00

"Jason Fried and David Hansson follow their own advice in REWORK, laying bare the surprising philosophies at the core of 37signals' success and inspiring us to put them into practice. There's no jarg......一起来看看 《Rework》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具