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 合并分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

VISUAL FOXPRO程序设计实用教程习题解答与实验指导

VISUAL FOXPRO程序设计实用教程习题解答与实验指导

康萍 / 中国经济出版社 / 2006-6 / 22.80元

本书为康萍等主编的《VisUal FosxPro程序设计实用教程》的配套习题解答与实验指导教材,由常年工作在计算机教学第一线的教师组织编写。全书共分为三部分:第1部分为实验指导,包括33个实验,分别讲述了每个实验的实验目的.实验内容和操作步骤;第2部分为开发实例,介绍了图书营理系统的分析、设计与实现过程;第3部分为配套教材各章节习题参考答案。   本书实验部分可操作性及实用性较强。系统开发案......一起来看看 《VISUAL FOXPRO程序设计实用教程习题解答与实验指导》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

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

HSV CMYK互换工具