hbase源码系列(一)Balancer 负载均衡
看源码很久了,终于开始动手写博客了,为什么是先写负载均衡呢,因为一个室友入职新公司了,然后他们遇到这方面的问题,某些机器的硬盘使用明显比别的机器要多,每次用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的构造函数里面。
hbase源码系列(一)Balancer 负载均衡的更多相关文章
- 11 hbase源码系列(十一)Put、Delete在服务端是如何处理
hbase源码系列(十一)Put.Delete在服务端是如何处理? 在讲完之后HFile和HLog之后,今天我想分享是Put在Region Server经历些了什么?相信前面看了<HTab ...
- hbase源码系列(十二)Get、Scan在服务端是如何处理
hbase源码系列(十二)Get.Scan在服务端是如何处理? 继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Del ...
- 9 hbase源码系列(九)StoreFile存储格式
hbase源码系列(九)StoreFile存储格式 从这一章开始要讲Region Server这块的了,但是在讲Region Server这块之前得讲一下StoreFile,否则后面的不好讲下去 ...
- 10 hbase源码系列(十)HLog与日志恢复
hbase源码系列(十)HLog与日志恢复 HLog概述 hbase在写入数据之前会先写入MemStore,成功了再写入HLog,当MemStore的数据丢失的时候,还可以用HLog的数据来进行恢 ...
- HBase源码系列之HFile
本文讨论0.98版本的hbase里v2版本.其实对于HFile能有一个大体的较深入理解是在我去查看"到底是不是一条记录不能垮block"的时候突然意识到的. 首先说一个对HFile ...
- hbase源码系列(十二)Get、Scan在服务端是如何处理?
继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Delete一样,上一篇我本来只打算写Put的,结果发现Delete也可以 ...
- hbase源码系列(二)HTable 探秘
hbase的源码终于搞一个段落了,在接下来的一个月,着重于把看过的源码提炼一下,对一些有意思的主题进行分享一下.继上一篇讲了负载均衡之后,这一篇我们从client开始讲吧,从client到master ...
- hbase源码系列(十五)终结篇&Scan续集-->如何查询出来下一个KeyValue
这是这个系列的最后一篇了,实在没精力写了,本来还想写一下hbck的,这个东西很常用,当hbase的Meta表出现错误的时候,它能够帮助我们进行修复,无奈看到3000多行的代码时,退却了,原谅我这点自私 ...
- hbase源码系列(六)HMaster启动过程
这一章是server端开始的第一章,有兴趣的朋友先去看一下hbase的架构图,我专门从网上弄下来的. 按照HMaster的run方法的注释,我们可以了解到它的启动过程会去做以下的动作. * <l ...
随机推荐
- 转:zTree树控件实战篇:针对多个下拉加载zTree树应该如何做出合理的配置
今天有一个zTree的朋友遇到一个非常棘手的问题,才研究zTree树控件两天就被上头催着看成果,很是苦恼.他面对的问题就是页面内多个地方需要下拉在其文本框下方加载zTree树,由于对zTree下拉加载 ...
- VS2005快捷键大全
快捷键功能 CTRL + SHIFT + B生成解决方案 CTRL + F7 生成编译 CTRL + O 打开文件 CTRL + SHIFT + O打开项目 CTRL + SHIFT + C显示类视图 ...
- django -- 对行的更新只有在save调用后才会入库
python3 manage.py shell Python 3.6.2 (v3.6.2:5fd33b5926, Jul 16 2017, 20:11:06) [GCC 4.2.1 (Apple In ...
- OOM killer(Out Of Memory killer)
最近接连遇到两个情况就是接连进程把kill掉 第一个情况就是有一个java进程被kill了.原因是我这个服务器上海部署了一个node服务,这个node服务大家都不熟悉.所以在使用的时候没有注意内存的使 ...
- [LeetCode] Paint House I & II
Paint House There are a row of n houses, each house can be painted with one of the three colors: red ...
- IOS 设备备份文件详解 (一)
IOS设备如果没有越狱的话想获取一些敏感的信息还是有写复杂的,比如获取上网信息,短信,通话记录等等这些,但是有一个通用的方法可以获取到这些信息,那就是IOS 设备的备份功能.文章不涉及如何备份以及恢复 ...
- 【Unity】12.4 通过网格分层选择行进路线
开发环境:Win10.Unity5.3.4.C#.VS2015 创建日期:2016-05-09 一.简介 在具体的游戏情景中,通过分层可以控制物体的行进路线,比如哪些物体只能住水面上行进,哪些物体只能 ...
- Retina屏的移动设备如何实现真正1px的线
前些日子总被人问起 iOS Retina 屏,设置 1px 边框,实际显示 2px,如何解决?原来一直没在意,源于自己根本不是像素眼……今天仔细瞅了瞅原生实现的边框和CSS设置的边框,确实差距不小…… ...
- Egret入门了解
0.前言 这个星期没有什么事做,就想找点技术了解一下.前段时间看过Egret,用来开发HTML5小游戏.一开始以为很麻烦的,但是经过这两天了解了一下,如果用这个游戏引擎来开发一些简单的游戏,还是蛮方便 ...
- 行为类模式(八):状态(State)
定义 当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类. 状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况.把状态的判断逻辑转移到表示不同状态的一系列类中,可以 ...