HMaster分析之Region的负载均衡实现(一)

        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移动的执行,具体的流程是怎么样的?

      且听下回分解~

时间: 2024-09-20 06:17:11

HMaster分析之Region的负载均衡实现(一)的相关文章

作一个支持过载自适应和动态扩容的负载均衡服务

摘要: 考虑一种情况,一组机器来提供一个服务,客户端要以相同的机会访问各台机器,而且其中一台机器负载过高的时候,要减少对这台服务器的访问,直到它的负载降低下来,而且如果我们添加了一台新的服务器,要把客户端的请求也均衡到这台新机器上. 思路及分析: 说到负载均衡,多半会用到哈希算法,比如说我们有a,b,c三台机器,我们会用一个很大的盒子去放这3台机器,比如这个盒子有10个格子,那我们这三台机器要均匀的放到各个格子里,如下: 1-a,2-b,3-c,4-a,5-b,6-c,7-a,8-b,9-c,1

集群、负载均衡、分布式

原文地址:http://www.uml.org.cn/zjjs/201108111.asp 1.集群 1.1定义:是一组独立的计算机系统构成一个松耦合的多处理器系统,它们之间通过网络实现进程间的通信.应用程序可以通过网络共享内存进行消息传送,实现分布式计算机. 是一组连在一起的计算机,从外部看它是一个系统,各节点可以是不同的操作系统或不同硬件构成的计算机.如一个提供Web服务的集群,对外界来看是一个大Web服务器.不过集群的节点也可以单独提供服务. 1.2负载均衡系统:集群中所有的节点都处于活动

解析nginx负载均衡

摘要:对于一个大型网站来说,负载均衡是永恒的话题.随着硬件技术的迅猛发展,越来越多的负载均衡硬件设备涌现出来,如F5 BIG-IP.Citrix NetScaler.Radware等等,虽然可以解决问题,但其高昂的价格却往往令人望而却步,因此负载均衡软件仍然是大部分公司的不二之选.nginx作为webserver的后起之秀,其优秀的反向代理功能和灵活的负载均衡策略受到了业界广泛的关注.本文将以工业生产为背景,从设计实现和具体应用等方面详细介绍nginx负载均衡策略. 关键字:nginx 负载均衡

云计算环境下的负载均衡算法的研究与设计

云计算环境下的负载均衡算法的研究与设计 北京邮电大学  冯秀玲 本文研究了云计算及集群的负载均衡机制和算法,提出了云计算环境下的集群负载均衡问题,并且基于两种不同的云计算场景:私有云和公有云,分别设计了两种不同的算法,对集中式和分布式的负载均衡算法进行了相应的改进,本文的主要内容包括: 第一,分析对比现有集中式算法的性能优劣,着重对典型的算法. 第二,研究分析现有的分布式负载均衡算法,并针对文献中随机游走算法的低效和不足问题进行了改进,改进后的分组随机游走算法属于分布式的,适合使用在公有云环境中

架构分析、数据整合、负载均衡,梦想旅行解析云上实践

全面赋能,双11电商解决方案上新,全新75折:https://www.aliyun.com/solution/ecommerce/act/huhang1111 9月23日由阿里云主办的第三期<电商大咖直播:备战双11最佳实践>线上分享圆满结束,来自梦想旅行的CTO李帅分享了如何在大数据的云上实践过程中来把畅行全球的事做的更完美,主要介绍了分布式爬虫架构.数据整合与知识发现.遇到的阻碍.高可用与容灾. 本次视频直播的整理文章整理完毕,如下内容. 如何能够让云计算更好的帮助行业的发展.更好的服务行

lvs、haproxy、nginx 负载均衡的比较分析

对软件实现负载均衡的几个软件,小D详细看了一下,从性能和稳定上还是LVS最牛,基本达到了F5硬件设备的60%性能,其他几个10%都有点困难.      不过就因为LVS忒牛了,配置也最麻烦了,而且健康检测需要另外配置Ldirector,其他HAPROXY和NGINX自己就用,而且配置超级简单.      所以小D建议,如果网站访问量不是门户级别的用HAPROXY或者NGINX就OK了,到了门户级别在用LVS+Idirector吧 哈哈     lvs和nginx都可以用作多机负载的方案,它们各有

负载均衡-apache 配置问题,请大家帮忙分析下

问题描述 apache 配置问题,请大家帮忙分析下 环境:apache 2.4(一台linux服务器),tongweb(两台linux服务器): 负载均衡跟session粘贴机制 sticky_session 均配置完成,但是有个问题,就是进入系统后, 看日志是访问的tongweb1,那么我关掉tongweb1后,在点击页面,就直接报404了,这是正常的吗? 是不是应该在我关掉tongweb1后,在点击页面应切换该访问到tongweb2呢? 当前会话不可用了,但是重新打开浏览器,新建会话后是可以

几种软负载均衡策略分析

公司去年上了F5,好用是好用,但是费用太高昂了,所以最近一直在研究软负载均衡这一块儿,恰巧今年年初谷歌开源了seesaw,让自己可以绕过很多弯路.特此总结下之前了解的负载均衡策略. -Sunface 在分布式系统中,负载均衡是非常重要的环节,通过负载均衡将请求派发到网络中的一个或多个节点上进行处理.通常来说,负载均衡分为硬件负载均衡及软件负载均衡.硬件负载均衡,顾名思义,在服务器节点之间安装专门的硬件进行负载均衡的工作,F5便为其中的佼佼者.软件负载均衡则是通过在服务器上安装的特定的负载均衡软件

一分钟了解阿里云产品:负载均衡五大热点技术问题分析

在上一篇文章中,我们为大家介绍负载均衡的概况,负载均衡就是对多台云服务器进行流量分发的服务,那在使用过程中,经常遇到的热门技术问题有哪些呢?     创建负载均衡实例:   https://help.aliyun.com/document_detail/slb/get-started/create-slb.html?spm=5176.product8314871_slb.6.92.w78Chv   负载均衡实例列表:   https://help.aliyun.com/document_deta