hbase源码系列(一)Balancer 负载均衡

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 看源码很久了,终于开始动手写博客了,为什么是先写负载均衡呢,因为一个室友入职新公司了,然后他们遇到这方面的问题,某些机器的硬盘使用明显比别的机器要多,每次用hadoop做完负载均衡,很快又变回来了。
看源码很久了,终于开始动手写博客了,为什么是先写负载均衡呢,因为一个室友入职新公司了,然后他们遇到这方面的问题,某些机器的硬盘使用明显比别的机器要多,每次用hadoop做完负载均衡,很快又变回来了。

首先我们先看HMaster当中怎么初始化Balancer的,把集群的状态穿进去,设置master,然后执行初始化。

//initialize load balancer
this.balancer.setClusterStatus(getClusterStatus());
this.balancer.setMasterServices(this);
this.balancer.initialize();

然后调用是在HMaster的balance()方法当中调用

Map<TableName, Map<ServerName, List<HRegionInfo>>> assignmentsByTable =
        this.assignmentManager.getRegionStates().getAssignmentsByTable();

List<RegionPlan> plans = new ArrayList<RegionPlan>();
//Give the balancer the current cluster state.
this.balancer.setClusterStatus(getClusterStatus());
//针对表来做平衡,返回平衡方案,针对全局,可能不是最优解
for (Map<ServerName, List<HRegionInfo>> assignments : assignmentsByTable.values()) {
    List<RegionPlan> partialPlans = this.balancer.balanceCluster(assignments);
    if (partialPlans != null) plans.addAll(partialPlans);
}
可以看到它首先获取了当前的集群的分配情况,这个分配情况是根据表的 Map<TableName, Map<ServerName, List<HRegionInfo>>,然后遍历这个map的values,调用balancer.balanceCluster(assignments) 来生成一个partialPlans,生成RegionPlan(Region的移动计划) 。

我们就可以切换到StochasticLoadBalancer当中了,这个是默认Balancer具体的实现了,也是最好的实现,下面就说说这玩意儿咋实现的。

看一下注释,这个玩意儿吹得神乎其神的,它说它考虑到了这么多因素:

* <ul>
 * <li>Region Load</li> Region的负载
 * <li>Table Load</li>  表的负载
 * <li>Data Locality</li> 数据本地性
 * <li>Memstore Sizes</li> 内存Memstore的大小
 * <li>Storefile Sizes</li> 硬盘存储文件的大小
 * </ul>

好,我们从balanceCluster开始看吧,一进来第一件事就是判断是否需要平衡。

//不需要平衡就退出
if (!needsBalance(new ClusterLoadState(clusterState))) {
   return null;
}

平衡的条件是:负载最大值和最小值要在平均值(region数/server数)的+-slop值之间, 但是这个平均值是基于表的,因为我们传进去的参数clusterState就是基于表的。

// Check if we even need to do any load balancing
// HBASE-3681 check sloppiness first
float average = cs.getLoadAverage(); // for logging
//集群的负载最大值和最小值要在平均值的+-slop值之间
int floor = (int) Math.floor(average * (1 - slop));
int ceiling = (int) Math.ceil(average * (1 + slop));
if (!(cs.getMinLoad() > ceiling || cs.getMaxLoad() < floor)) {
    .....return false;
}
return true;
如果需要平衡的话,就开始计算开销了。
// Keep track of servers to iterate through them.
Cluster cluster = new Cluster(clusterState, loads, regionFinder);
//计算出来当前的开销    
double currentCost = computeCost(cluster, Double.MAX_VALUE);
double initCost = currentCost;
double newCost = currentCost;
 for (step = 0; step < computedMaxSteps; step++) {
         //随机挑选一个"选号器"
         int pickerIdx = RANDOM.nextInt(pickers.length);
         RegionPicker p = pickers[pickerIdx];
         //用选号器从集群当中随机跳出一对来,待处理的<server,region>对
         Pair<Pair<Integer, Integer>, Pair<Integer, Integer>> picks = p.pick(cluster);
         int leftServer = picks.getFirst().getFirst();
         int leftRegion = picks.getFirst().getSecond();
         int rightServer = picks.getSecond().getFirst();
         int rightRegion = picks.getSecond().getSecond();
         cluster.moveOrSwapRegion(leftServer,
              rightServer,
              leftRegion,
              rightRegion);
        //移动或者交换完之后,看看新的开销是否要继续
         newCost = computeCost(cluster, currentCost);
         // Should this be kept? 挺好,保存新状态
         if (newCost < currentCost) {
            currentCost = newCost;
         } else {
   // 操作不划算,就回退
           cluster.moveOrSwapRegion(leftServer,
                rightServer,
                rightRegion,
                leftRegion);
       }
     if (initCost > currentCost) {
         //找到了满意的平衡方案
         List<RegionPlan> plans = createRegionPlans(cluster);
         return plans;
    }
上面的被我清除了细枝末节之后的代码主体,okay,上面逻辑过程如下:

1. 生成一个虚拟的集群cluster,方便计算计算当前状态的开销,其中clusterState是表的状态,loads是整个集群的状态。

// Keep track of servers to iterate through them.
Cluster cluster = new Cluster(clusterState, loads, regionFinder);
//计算出来当前的开销    
double currentCost = computeCost(cluster, Double.MAX_VALUE);
double initCost = currentCost;
double newCost = currentCost;

2. 然后循环computedMaxSteps次,随机从选出一个picker来计算平衡方案。

int pickerIdx = RANDOM.nextInt(pickers.length);
RegionPicker p = pickers[pickerIdx];
//用选号器从集群当中随机跳出一对来,待处理的<server,region>对
Pair<Pair<Integer, Integer>, Pair<Integer, Integer>> picks = p.pick(cluster);

picker是啥?这里面有三个,第一个是RandomRegionPicker是随机挑选region,这里就不详细介绍了,主要讨论后面两个;第二个LoadPicker是计算负载的,第三个主要是考虑本地性的。给我感觉就很像ZF的摇号器一样,用哪种算法还要摇个号。

pickers = new RegionPicker[] {
      new RandomRegionPicker(),
      new LoadPicker(),
      localityPicker
};

下面我们先看localityPicker的pick方法,这个方法是随机抽选出来一个server、region,找出region的其他本地机器,然后他们返回。

@Override
    Pair<Pair<Integer, Integer>, Pair<Integer, Integer>> pick(Cluster cluster) {
      if (this.masterServices == null) {
        return new Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>(
            new Pair<Integer, Integer>(-1,-1),
            new Pair<Integer, Integer>(-1,-1)
        );
      }
      // Pick a random region server 随机选出一个server来
      int thisServer = pickRandomServer(cluster);

      // Pick a random region on this server 随机选出region
      int thisRegion = pickRandomRegion(cluster, thisServer, 0.0f);

      if (thisRegion == -1) {
        return new Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>(
            new Pair<Integer, Integer>(-1,-1),
            new Pair<Integer, Integer>(-1,-1)
        );
      }

      // Pick the server with the highest locality 找出本地性最高的目标server
      int otherServer = pickHighestLocalityServer(cluster, thisServer, thisRegion);

      // pick an region on the other server to potentially swap
      int otherRegion = this.pickRandomRegion(cluster, otherServer, 0.5f);

      return new Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>(
          new Pair<Integer, Integer>(thisServer,thisRegion),
          new Pair<Integer, Integer>(otherServer,otherRegion)
      );
    }

okay,这个结束了,下面我们看看LoadPicker吧。

@Override
    Pair<Pair<Integer, Integer>, Pair<Integer, Integer>> pick(Cluster cluster) {
      cluster.sortServersByRegionCount();
      //先挑选出负载最高的server
      int thisServer = pickMostLoadedServer(cluster, -1);
      //再选出除了负载最高的server之外负载最低的server
      int otherServer = pickLeastLoadedServer(cluster, thisServer);

      Pair<Integer, Integer> regions = pickRandomRegions(cluster, thisServer, otherServer);
      return new Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>(
          new Pair<Integer, Integer>(thisServer, regions.getFirst()),
          new Pair<Integer, Integer>(otherServer, regions.getSecond())

      );
    }
这里的负载高和负载低是按照Server上面的region数来算的,而不是存储文件啥的,选出负载最高和负载最低的时候,又随机抽出region来返回了。

pick挑选的过程介绍完了,那么很明显,计算才是重头戏了,什么样的region会导致计算出来的分数高低呢?

3. 重点在计算函数上 computeCost(cluster, Double.MAX_VALUE) 结果这个函数也超级简单,哈哈~

protected double computeCost(Cluster cluster, double previousCost) {
    double total = 0;
    
    for (CostFunction c:costFunctions) {
      if (c.getMultiplier() <= 0) {
        continue;
      }

      total += c.getMultiplier() * c.cost(cluster);

      if (total > previousCost) {
        return total;
      }
    }
    return total;
  }
遍历CostFunction,拿cost的加权平均和计算出来。

那costFunction里面都有啥呢?localityCost又出现了,看来本地性是一个很大的考虑的情况。

costFunctions = new CostFunction[]{
      new RegionCountSkewCostFunction(conf),
      new MoveCostFunction(conf),
      localityCost,
      new TableSkewCostFunction(conf),
      regionLoadFunctions[0],
      regionLoadFunctions[1],
      regionLoadFunctions[2],
      regionLoadFunctions[3],
};

 regionLoadFunctions = new CostFromRegionLoadFunction[] {
        new ReadRequestCostFunction(conf),
        new WriteRequestCostFunction(conf),
        new MemstoreSizeCostFunction(conf),
        new StoreFileCostFunction(conf)
     };

可以看出来,里面真正看中硬盘内容大小的,只有一个StoreFileCostFunction,cost的计算方式有些区别,但都是一个0-1之间的数字,下面给出里面5个函数都用过的cost的函数。

//cost函数
double max = ((count - 1) * mean) + (total - mean);
for (double n : stats) {
        double diff = Math.abs(mean - n);
        totalCost += diff;
}

double scaled =  scale(0, max, totalCost);
return scaled;

//scale函数
protected double scale(double min, double max, double value) {
      if (max == 0 || value == 0) {
        return 0;
      }

      return Math.max(0d, Math.min(1d, (value - min) / max));
}

经过分析吧,我觉得影响里面最后cost最大的是它的权重,下面给一下,这些function的默认权重。

RegionCountSkewCostFunction hbase.master.balancer.stochastic.regionCountCost ,默认值500

MoveCostFunction hbase.master.balancer.stochastic.moveCost,默认值是100

localityCost hbase.master.balancer.stochastic.localityCost,默认值是25

TableSkewCostFunction hbase.master.balancer.stochastic.tableSkewCost,默认值是35

ReadRequestCostFunction hbase.master.balancer.stochastic.readRequestCost,默认值是5

WriteRequestCostFunction hbase.master.balancer.stochastic.writeRequestCost,默认值是5

MemstoreSizeCostFunction hbase.master.balancer.stochastic.memstoreSizeCost,默认值是5

StoreFileCostFunction hbase.master.balancer.stochastic.storefileSizeCost,默认值是5

Storefile的默认值是5,那么低。。。可以试着提高一下这个参数,使它在计算cost消耗的时候,产生更加正向的意义,效果不好说。

4. 根据虚拟的集群状态生成RegionPlan,这里就不说了

List<RegionPlan> plans = createRegionPlans(cluster);

源码的分析完毕,要想减少存储内容分布不均匀,可以试着考虑增加一个picker,这样又不会缺少对其他条件的考虑,具体可以参考LoadPicker,复制它的实现再写一个,在pickMostLoadedServer和pickLeastLoadedServer这两个方法里面把考虑的条件改一下,以前的条件是Integer[] servers = cluster.serverIndicesSortedByRegionCount; 通过这个来查找一下负载最高和最低的server,那么现在我们要在Cluster里面增加一个Server ---> StoreFile大小的关系映射集合,但是这里面没有,只有regionLoads,RegionLoad这个类有一个方法getStorefileSizeMB可以获得StoreFile的大小,我们通过里面的region和server的映射regionIndexToServerIndex来最后计算出来这个映射关系即可,这个计算映射关系个过程放在Cluster的构造函数里面。

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
7月前
|
负载均衡 算法 网络协议
Ribbon 负载均衡源码解读
Ribbon 负载均衡源码解读
75 15
Ribbon 负载均衡源码解读
|
6月前
|
负载均衡 算法 调度
负载均衡原理分析与源码解读
负载均衡原理分析与源码解读
|
7月前
|
负载均衡 Java API
Feign 进行rpc 调用时使用ribbon负载均衡源码解析
Feign 进行rpc 调用时使用ribbon负载均衡源码解析
94 11
|
9月前
|
SQL Java 分布式数据库
实现HBase表和RDB表的转化(附Java源码资源)
该文介绍了如何将数据从RDB转换为HBase表,主要涉及三个来源:RDB Table、Client API和Files。文章重点讲解了RDB到HBase的转换,通过批处理思想,利用RDB接口批量导出数据并转化为`List&lt;Put&gt;`,然后导入HBase。目录结构包括配置文件、RDB接口及实现类、HBase接口及实现类,以及一个通用转换器接口和实现。代码中,`RDBImpl`负责从RDB读取数据并构造`Put`对象,`HBaseImpl`则负责将`Put`写入HBase表。整个过程通过配置文件`transfer.properties`管理HBase和RDB的映射关系。
73 3
实现HBase表和RDB表的转化(附Java源码资源)
|
9月前
|
存储 缓存 负载均衡
【Apache ShenYu源码】如何实现负载均衡模块设计
整个模块为ShenYu提供了什么功能。我们可以看下上文我们提到的工厂对象。/***/核心方法很清晰,我们传入Upsteam列表,通过这个模块的负载均衡算法,负载均衡地返回其中一个对象。这也就是这个模块提供的功能。
108 1
|
设计模式 负载均衡 Apache
SpringCloud源码剖析-Zuul使用Ribbon负载均衡-RibbonRoutingFilter
RibbonCommandContext 在run方法中构建了一个 RibbonCommandContext Ribbon的上下文对象,然后调用 forward 方法转发请求 ,通过 setResponse方法设置结果
220 0
|
9月前
|
负载均衡 算法 Java
SpringCloud负载均衡源码解析 | 带你从表层一步步剖析Ribbon组件如何实现负载均衡功能
SpringCloud负载均衡源码解析 | 带你从表层一步步剖析Ribbon组件如何实现负载均衡功能
199 0
|
9月前
|
消息中间件 负载均衡 算法
RocketMQ源码(三)简单探索Producer和Consumer与Queue之间的负载均衡策略
- Producer如何将消息负载均衡发送给queue? - Consumer如何通过负载均衡并发消费queue的消息?
613 0
|
设计模式 负载均衡 Apache
二十.SpringCloud源码剖析-Zuul使用Ribbon负载均衡-RibbonRoutingFilter
经过前面几章的学习,我们对Zuul的的详细执行流程,以及内置的Filter都有了一些认识,本篇文章是针对RibbonRoutingFilter做一个详细的分析,跟一下它是如何使用Ribbon对下游的微服务进行负载均衡的。注意:这篇文章是在 《zuul的执行流程》基础上进行延伸的,另外Ribbon的原理见:《Ribbon负载均衡原理》
|
存储 负载均衡 大数据
分布式数据库HBase的重要机制和原理的负载均衡原理
在当今的互联网时代,数据的存储和处理已经成为了企业的核心竞争力之一。而在大数据领域,分布式数据库HBase作为一个开源的分布式数据库系统,因其高性能、高可靠性和易于扩展性等特点,受到了广泛的应用。本文将深入探讨HBase中的重要机制之一:负载均衡原理,帮助开发者更好地理解和掌握HBase的工作原理。
464 0