HBase中Region是表按行方向切分的一个个数据区域,由RegionServer负责管理,并向外提供数据读写服务。如果一个RegionServer上的Region过多,那么该RegionServer对应的就会承担过多的读写等服务请求,也就有可能在高并发访问的情况下,造成服务器性能下降甚至宕机。如此,RegionServer间Region的动态负载均衡,也就成了HBase实现高性能读写请求访问的一个需要解决的问题。那么,Region是如何在RegionServer间动态负载均衡的呢?
在HMaster中,有几个成员变量定义如下:
LoadBalancer balancer;// 实现Region动态负载均衡的实体
private BalancerChore balancerChore;// 完成动态负载均衡的工作线程
// Tracker for load balancer state
// 加载balancer状态的跟踪器
LoadBalancerTracker loadBalancerTracker;
那么,这三个变量在HMaster启动时,是如何初始化的呢?
在HMaster的构造函数中,有如下调用:
// 开启Master的各种管理者
startActiveMasterManager(infoPort);
startActiveMasterManager()方法中,又有如下调用:
finishActiveMasterInitialization(status);
接下来,finishActiveMasterInitialization()方法中,开始了动态负载均衡中用到的各实体的初始化。代码如下:
initializeZKBasedSystemTrackers();
//initialize load balancer
// 初始化balancer
this.balancer.setClusterStatus(getClusterStatus());
this.balancer.setMasterServices(this);
this.balancer.initialize();
// 创建工作线程balancerChore,它会周期性的调用HMaster的balance()方法,调用周期为参数
// hbase.balancer.period配置的值,未配置的话默认为5分钟
this.balancerChore = new BalancerChore(this);
Threads.setDaemonThreadRunning(balancerChore.getThread());
而initializeZKBasedSystemTrackers()方法中,完成了三个相关组件的初始化,从而可以实现finishActiveMasterInitialization()方法中后面对balancer的设置。
// 初始化LoadBalancer类型的成员变量balancer
// 利用发射技术,优先加载hbase.master.loadbalancer.class参数配置的均衡器类,
// 参数未配置再加载StochasticLoadBalancer
this.balancer = LoadBalancerFactory.getLoadBalancer(conf);
// 初始化LoadBalancerTracker类型的成员变量loadBalancerTracker
this.loadBalancerTracker = new LoadBalancerTracker(zooKeeper, this);
// 启动loadBalancerTracker
this.loadBalancerTracker.start();
首先,介绍下BalancerChore类型的成员变量balancerChore,它为一个后台线程,会周期性的调用HMaster的balance()方法,周期性的完成实际的Region的负载均衡。它的定义如下:
/**
* Chore that will call HMaster.balance{@link org.apache.hadoop.hbase.master.HMaster#balance()} when
* needed.
*
* 在需要的时候钓调用HMaster的balance()方法的工作线程。
*/
@InterfaceAudience.Private
public class BalancerChore extends Chore {
private static final Log LOG = LogFactory.getLog(BalancerChore.class);
private final HMaster master;
// 构造方法,线程的名称为master的serverName + "-BalancerChore",
// chore()方法循环调用周期为参数hbase.balancer.period,默认为5分钟
public BalancerChore(HMaster master) {
super(master.getServerName() + "-BalancerChore",
master.getConfiguration().getInt("hbase.balancer.period", 300000),
master);
this.master = master;
}
/**
* 线程的run()方法会周期性的调用chore()方法
*/
@Override
protected void chore() {
try {
// 调用HMaster的balance()方法,完成实际的Region动态负载均衡
master.balance();
} catch (IOException e) {
LOG.error("Failed to balance.", e);
}
}
}
它的父类Chore为HBase中的抽象线程类,HBase中很多工作线程都是继承自Chore,它的run()方法定义如下:
/**
* @see java.lang.Thread#run()
*/
@Override
public void run() {
try {
boolean initialChoreComplete = false;
while (!this.stopper.isStopped()) {
// 开始时间
long startTime = System.currentTimeMillis();
try {
// 如果是第一次循环,完成初始化工作
if (!initialChoreComplete) {
initialChoreComplete = initialChore();
} else {
// 第一次后的每次循环,则周期性的调用chore()方法
chore();
}
} catch (Exception e) {
LOG.error("Caught exception", e);
if (this.stopper.isStopped()) {
continue;
}
}
// 睡眠期睡眠一定的时间,然后再去调用chore()方法
this.sleeper.sleep(startTime);
}
} catch (Throwable t) {
LOG.fatal(getName() + "error", t);
} finally {
LOG.info(getName() + " exiting");
cleanup();
}
}
下面,我们再重点分析下HMaster的balance()方法,源码如下:
public boolean balance() throws IOException {
// if master not initialized, don't run balancer.
// 如果master没有被初始化,不能运行balancer
if (!this.initialized) {
LOG.debug("Master has not been initialized, don't run balancer.");
return false;
}
// Do this call outside of synchronized block.
int maximumBalanceTime = getBalancerCutoffTime();
synchronized (this.balancer) {// 在this.balancer上同步
// If balance not true, don't run balancer.
// 从loadBalancerTracker处获取balancer是否已开启,如果没有,则返回false
if (!this.loadBalancerTracker.isBalancerOn()) return false;
// Only allow one balance run at at time.
if (this.assignmentManager.getRegionStates().isRegionsInTransition()) {
Map<String, RegionState> regionsInTransition =
this.assignmentManager.getRegionStates().getRegionsInTransition();
LOG.debug("Not running balancer because " + regionsInTransition.size() +
" region(s) in transition: " + org.apache.commons.lang.StringUtils.
abbreviate(regionsInTransition.toString(), 256));
return false;
}
if (this.serverManager.areDeadServersInProgress()) {
LOG.debug("Not running balancer because processing dead regionserver(s): " +
this.serverManager.getDeadServers());
return false;
}
if (this.cpHost != null) {
try {
if (this.cpHost.preBalance()) {
LOG.debug("Coprocessor bypassing balancer request");
return false;
}
} catch (IOException ioe) {
LOG.error("Error invoking master coprocessor preBalance()", ioe);
return false;
}
}
// 获取表名->{ServerName->Region列表的映射集合}的映射集合assignmentsByTable
Map<TableName, Map<ServerName, List<HRegionInfo>>> assignmentsByTable =
this.assignmentManager.getRegionStates().getAssignmentsByTable();
List<RegionPlan> plans = new ArrayList<RegionPlan>();
//Give the balancer the current cluster state.
// 设置balancer中集群最新的状态
this.balancer.setClusterStatus(getClusterStatus());
// 循环assignmentsByTable中的value:每个表的ServerName->Region列表的映射集合
for (Map<ServerName, List<HRegionInfo>> assignments : assignmentsByTable.values()) {
List<RegionPlan> partialPlans = this.balancer.balanceCluster(assignments);
if (partialPlans != null) plans.addAll(partialPlans);
}
long cutoffTime = System.currentTimeMillis() + maximumBalanceTime;
// 移动的Region总个数
int rpCount = 0; // number of RegionPlans balanced so far
// Region的移动计划总耗时
long totalRegPlanExecTime = 0;
// Region的移动计划不为空
if (plans != null && !plans.isEmpty()) {
// 循环处理
for (RegionPlan plan: plans) {
LOG.info("balance " + plan);
// 开始时间
long balStartTime = System.currentTimeMillis();
//TODO: bulk assign
// 调用assignmentManager的balance()方法执行计划
this.assignmentManager.balance(plan);
// 累加Region的移动计划总耗时
totalRegPlanExecTime += System.currentTimeMillis()-balStartTime;
// 累加移动的Region总个数
rpCount++;
if (rpCount < plans.size() &&
// if performing next balance exceeds cutoff time, exit the loop
// 如果完成下一个balance的时间超过cutoffTime,退出循环
// 这个完成时间是预估的,Region移动的平均耗时,用一个粗略的算法,已完成Region移动的总 耗时/已完成Region移动的总个数
(System.currentTimeMillis() + (totalRegPlanExecTime / rpCount)) > cutoffTime) {
//TODO: After balance, there should not be a cutoff time (keeping it as a security net for now)
LOG.debug("No more balancing till next balance run; maximumBalanceTime=" +
maximumBalanceTime);
break;
}
}
}
// 如果协处理器主机不为空,运行协处理器的钩子方法postBalance()
if (this.cpHost != null) {
try {
this.cpHost.postBalance(rpCount < plans.size() ? plans.subList(0, rpCount) : plans);
} catch (IOException ioe) {
// balancing already succeeded so don't change the result
LOG.error("Error invoking master coprocessor postBalance()", ioe);
}
}
}
// If LoadBalancer did not generate any plans, it means the cluster is already balanced.
// Return true indicating a success.
return true;
}
balance()方法中,首先会利用HMaster中的成员变量assignmentManager,获取表名->{ServerName->Region列表的映射集合}的映射集合assignmentsByTable,如下:
// 获取表名->{ServerName->Region列表的映射集合}的映射集合assignmentsByTable
Map<TableName, Map<ServerName, List<HRegionInfo>>> assignmentsByTable =
this.assignmentManager.getRegionStates().getAssignmentsByTable();
然后,通过上面提到的HMaster的成员变量balancer的balanceCluster()方法,获得Region的 移动计划列表,添加到数据结构List<RegionPlan>类型的plans中。如下:
// 循环assignmentsByTable中的value:每个表的ServerName->Region列表的映射集合
for (Map<ServerName, List<HRegionInfo>> assignments : assignmentsByTable.values()) {
List<RegionPlan> partialPlans = this.balancer.balanceCluster(assignments);
if (partialPlans != null) plans.addAll(partialPlans);
}
紧接着,循环处理这个移动计划列表plans,开始移动Region,如下:
// Region的移动计划不为空
if (plans != null && !plans.isEmpty()) {
// 循环处理
for (RegionPlan plan: plans) {
LOG.info("balance " + plan);
// 开始时间
long balStartTime = System.currentTimeMillis();
//TODO: bulk assign
// 调用assignmentManager的balance()方法执行计划
this.assignmentManager.balance(plan);
// 累加Region的移动计划总耗时
totalRegPlanExecTime += System.currentTimeMillis()-balStartTime;
// 累加移动的Region总个数
rpCount++;
if (rpCount < plans.size() &&
// if performing next balance exceeds cutoff time, exit the loop
// 如果完成下一个balance的时间超过cutoffTime,退出循环
// 这个完成时间是预估的,Region移动的平均耗时,用一个粗略的算法,已完成Region移动的总 耗时/已完成Region移动的总个数
(System.currentTimeMillis() + (totalRegPlanExecTime / rpCount)) > cutoffTime) {
//TODO: After balance, there should not be a cutoff time (keeping it as a security net for now)
LOG.debug("No more balancing till next balance run; maximumBalanceTime=" +
maximumBalanceTime);
break;
}
}
}
移动计划的执行,实际上是由assignmentManager的balance()实现的,并且,在执行移动计划时,会根据以往执行过的计划的平均耗时是否超过一定阈值,来确定是继续此移动计划还是跳过转而执行下一个。
最后,如果协处理器主机不为空,运行协处理器的钩子方法postBalance(),如下:
// 如果协处理器主机不为空,运行协处理器的钩子方法postBalance()
if (this.cpHost != null) {
try {
this.cpHost.postBalance(rpCount < plans.size() ? plans.subList(0, rpCount) : plans);
} catch (IOException ioe) {
// balancing already succeeded so don't change the result
LOG.error("Error invoking master coprocessor postBalance()", ioe);
}
}
}
那么,最关键的几个问题,就是:
1、需要移动的Region是如何被选中,它又要被移动往哪里?
2、Region移动的执行,具体的流程是怎么样的?
且听下回分解~