001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.hbase.rsgroup; 020 021import java.io.IOException; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030import java.util.TreeMap; 031 032import org.apache.hadoop.conf.Configuration; 033import org.apache.hadoop.hbase.ClusterMetrics; 034import org.apache.hadoop.hbase.HBaseIOException; 035import org.apache.hadoop.hbase.HConstants; 036import org.apache.hadoop.hbase.ServerName; 037import org.apache.hadoop.hbase.TableName; 038import org.apache.hadoop.hbase.client.RegionInfo; 039import org.apache.hadoop.hbase.constraint.ConstraintException; 040import org.apache.hadoop.hbase.master.LoadBalancer; 041import org.apache.hadoop.hbase.master.MasterServices; 042import org.apache.hadoop.hbase.master.RegionPlan; 043import org.apache.hadoop.hbase.master.balancer.StochasticLoadBalancer; 044import org.apache.hadoop.hbase.net.Address; 045import org.apache.hadoop.util.ReflectionUtils; 046import org.apache.yetus.audience.InterfaceAudience; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; 051import org.apache.hbase.thirdparty.com.google.common.collect.ArrayListMultimap; 052import org.apache.hbase.thirdparty.com.google.common.collect.LinkedListMultimap; 053import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap; 054import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 055import org.apache.hbase.thirdparty.com.google.common.collect.Maps; 056 057/** 058 * GroupBasedLoadBalancer, used when Region Server Grouping is configured (HBase-6721) 059 * It does region balance based on a table's group membership. 060 * 061 * Most assignment methods contain two exclusive code paths: Online - when the group 062 * table is online and Offline - when it is unavailable. 063 * 064 * During Offline, assignments are assigned based on cached information in zookeeper. 065 * If unavailable (ie bootstrap) then regions are assigned randomly. 066 * 067 * Once the GROUP table has been assigned, the balancer switches to Online and will then 068 * start providing appropriate assignments for user tables. 069 * 070 */ 071@InterfaceAudience.Private 072public class RSGroupBasedLoadBalancer implements RSGroupableBalancer { 073 private static final Logger LOG = LoggerFactory.getLogger(RSGroupBasedLoadBalancer.class); 074 075 private Configuration config; 076 private ClusterMetrics clusterStatus; 077 private MasterServices masterServices; 078 private volatile RSGroupInfoManager rsGroupInfoManager; 079 private LoadBalancer internalBalancer; 080 081 /** 082 * Used by reflection in {@link org.apache.hadoop.hbase.master.balancer.LoadBalancerFactory}. 083 */ 084 @InterfaceAudience.Private 085 public RSGroupBasedLoadBalancer() {} 086 087 @Override 088 public Configuration getConf() { 089 return config; 090 } 091 092 @Override 093 public void setConf(Configuration conf) { 094 this.config = conf; 095 } 096 097 @Override 098 public void setClusterMetrics(ClusterMetrics sm) { 099 this.clusterStatus = sm; 100 } 101 102 @Override 103 public void setMasterServices(MasterServices masterServices) { 104 this.masterServices = masterServices; 105 } 106 107 @Override 108 public List<RegionPlan> balanceCluster(TableName tableName, Map<ServerName, List<RegionInfo>> 109 clusterState) throws HBaseIOException { 110 return balanceCluster(clusterState); 111 } 112 113 @Override 114 public List<RegionPlan> balanceCluster(Map<ServerName, List<RegionInfo>> clusterState) 115 throws HBaseIOException { 116 if (!isOnline()) { 117 throw new ConstraintException(RSGroupInfoManager.RSGROUP_TABLE_NAME + 118 " is not online, unable to perform balance"); 119 } 120 121 Map<ServerName,List<RegionInfo>> correctedState = correctAssignments(clusterState); 122 List<RegionPlan> regionPlans = new ArrayList<>(); 123 124 List<RegionInfo> misplacedRegions = correctedState.get(LoadBalancer.BOGUS_SERVER_NAME); 125 for (RegionInfo regionInfo : misplacedRegions) { 126 ServerName serverName = findServerForRegion(clusterState, regionInfo); 127 regionPlans.add(new RegionPlan(regionInfo, serverName, null)); 128 } 129 try { 130 // Record which region servers have been processed,so as to skip them after processed 131 HashSet<ServerName> processedServers = new HashSet<>(); 132 133 // For each rsgroup 134 for (RSGroupInfo rsgroup : rsGroupInfoManager.listRSGroups()) { 135 Map<ServerName, List<RegionInfo>> groupClusterState = new HashMap<>(); 136 Map<TableName, Map<ServerName, List<RegionInfo>>> groupClusterLoad = new HashMap<>(); 137 for (ServerName server : clusterState.keySet()) { // for each region server 138 if (!processedServers.contains(server) // server is not processed yet 139 && rsgroup.containsServer(server.getAddress())) { // server belongs to this rsgroup 140 List<RegionInfo> regionsOnServer = correctedState.get(server); 141 groupClusterState.put(server, regionsOnServer); 142 143 processedServers.add(server); 144 } 145 } 146 147 groupClusterLoad.put(HConstants.ENSEMBLE_TABLE_NAME, groupClusterState); 148 this.internalBalancer.setClusterLoad(groupClusterLoad); 149 List<RegionPlan> groupPlans = this.internalBalancer 150 .balanceCluster(groupClusterState); 151 if (groupPlans != null) { 152 regionPlans.addAll(groupPlans); 153 } 154 } 155 } catch (IOException exp) { 156 LOG.warn("Exception while balancing cluster.", exp); 157 regionPlans.clear(); 158 } 159 return regionPlans; 160 } 161 162 @Override 163 public Map<ServerName, List<RegionInfo>> roundRobinAssignment( 164 List<RegionInfo> regions, List<ServerName> servers) throws HBaseIOException { 165 Map<ServerName, List<RegionInfo>> assignments = Maps.newHashMap(); 166 ListMultimap<String,RegionInfo> regionMap = ArrayListMultimap.create(); 167 ListMultimap<String,ServerName> serverMap = ArrayListMultimap.create(); 168 generateGroupMaps(regions, servers, regionMap, serverMap); 169 for(String groupKey : regionMap.keySet()) { 170 if (regionMap.get(groupKey).size() > 0) { 171 Map<ServerName, List<RegionInfo>> result = 172 this.internalBalancer.roundRobinAssignment( 173 regionMap.get(groupKey), 174 serverMap.get(groupKey)); 175 if(result != null) { 176 if(result.containsKey(LoadBalancer.BOGUS_SERVER_NAME) && 177 assignments.containsKey(LoadBalancer.BOGUS_SERVER_NAME)){ 178 assignments.get(LoadBalancer.BOGUS_SERVER_NAME).addAll( 179 result.get(LoadBalancer.BOGUS_SERVER_NAME)); 180 } else { 181 assignments.putAll(result); 182 } 183 } 184 } 185 } 186 return assignments; 187 } 188 189 @Override 190 public Map<ServerName, List<RegionInfo>> retainAssignment( 191 Map<RegionInfo, ServerName> regions, List<ServerName> servers) throws HBaseIOException { 192 try { 193 Map<ServerName, List<RegionInfo>> assignments = new TreeMap<>(); 194 ListMultimap<String, RegionInfo> groupToRegion = ArrayListMultimap.create(); 195 Set<RegionInfo> misplacedRegions = getMisplacedRegions(regions); 196 for (RegionInfo region : regions.keySet()) { 197 if (!misplacedRegions.contains(region)) { 198 String groupName = rsGroupInfoManager.getRSGroupOfTable(region.getTable()); 199 if (groupName == null) { 200 LOG.info("Group not found for table " + region.getTable() + ", using default"); 201 groupName = RSGroupInfo.DEFAULT_GROUP; 202 } 203 groupToRegion.put(groupName, region); 204 } 205 } 206 // Now the "groupToRegion" map has only the regions which have correct 207 // assignments. 208 for (String key : groupToRegion.keySet()) { 209 Map<RegionInfo, ServerName> currentAssignmentMap = new TreeMap<RegionInfo, ServerName>(); 210 List<RegionInfo> regionList = groupToRegion.get(key); 211 RSGroupInfo info = rsGroupInfoManager.getRSGroup(key); 212 List<ServerName> candidateList = filterOfflineServers(info, servers); 213 for (RegionInfo region : regionList) { 214 currentAssignmentMap.put(region, regions.get(region)); 215 } 216 if(candidateList.size() > 0) { 217 assignments.putAll(this.internalBalancer.retainAssignment( 218 currentAssignmentMap, candidateList)); 219 } 220 } 221 222 for (RegionInfo region : misplacedRegions) { 223 String groupName = rsGroupInfoManager.getRSGroupOfTable(region.getTable()); 224 if (groupName == null) { 225 LOG.info("Group not found for table " + region.getTable() + ", using default"); 226 groupName = RSGroupInfo.DEFAULT_GROUP; 227 } 228 RSGroupInfo info = rsGroupInfoManager.getRSGroup(groupName); 229 List<ServerName> candidateList = filterOfflineServers(info, servers); 230 ServerName server = this.internalBalancer.randomAssignment(region, 231 candidateList); 232 if (server != null) { 233 if (!assignments.containsKey(server)) { 234 assignments.put(server, new ArrayList<>()); 235 } 236 assignments.get(server).add(region); 237 } else { 238 //if not server is available assign to bogus so it ends up in RIT 239 if(!assignments.containsKey(LoadBalancer.BOGUS_SERVER_NAME)) { 240 assignments.put(LoadBalancer.BOGUS_SERVER_NAME, new ArrayList<>()); 241 } 242 assignments.get(LoadBalancer.BOGUS_SERVER_NAME).add(region); 243 } 244 } 245 return assignments; 246 } catch (IOException e) { 247 throw new HBaseIOException("Failed to do online retain assignment", e); 248 } 249 } 250 251 @Override 252 public ServerName randomAssignment(RegionInfo region, 253 List<ServerName> servers) throws HBaseIOException { 254 ListMultimap<String,RegionInfo> regionMap = LinkedListMultimap.create(); 255 ListMultimap<String,ServerName> serverMap = LinkedListMultimap.create(); 256 generateGroupMaps(Lists.newArrayList(region), servers, regionMap, serverMap); 257 List<ServerName> filteredServers = serverMap.get(regionMap.keySet().iterator().next()); 258 return this.internalBalancer.randomAssignment(region, filteredServers); 259 } 260 261 private void generateGroupMaps( 262 List<RegionInfo> regions, 263 List<ServerName> servers, 264 ListMultimap<String, RegionInfo> regionMap, 265 ListMultimap<String, ServerName> serverMap) throws HBaseIOException { 266 try { 267 for (RegionInfo region : regions) { 268 String groupName = rsGroupInfoManager.getRSGroupOfTable(region.getTable()); 269 if (groupName == null) { 270 LOG.info("Group not found for table " + region.getTable() + ", using default"); 271 groupName = RSGroupInfo.DEFAULT_GROUP; 272 } 273 regionMap.put(groupName, region); 274 } 275 for (String groupKey : regionMap.keySet()) { 276 RSGroupInfo info = rsGroupInfoManager.getRSGroup(groupKey); 277 serverMap.putAll(groupKey, filterOfflineServers(info, servers)); 278 if(serverMap.get(groupKey).size() < 1) { 279 serverMap.put(groupKey, LoadBalancer.BOGUS_SERVER_NAME); 280 } 281 } 282 } catch(IOException e) { 283 throw new HBaseIOException("Failed to generate group maps", e); 284 } 285 } 286 287 private List<ServerName> filterOfflineServers(RSGroupInfo RSGroupInfo, 288 List<ServerName> onlineServers) { 289 if (RSGroupInfo != null) { 290 return filterServers(RSGroupInfo.getServers(), onlineServers); 291 } else { 292 LOG.warn("RSGroup Information found to be null. Some regions might be unassigned."); 293 return Collections.EMPTY_LIST; 294 } 295 } 296 297 /** 298 * Filter servers based on the online servers. 299 * 300 * @param servers 301 * the servers 302 * @param onlineServers 303 * List of servers which are online. 304 * @return the list 305 */ 306 private List<ServerName> filterServers(Set<Address> servers, 307 List<ServerName> onlineServers) { 308 /** 309 * servers is actually a TreeSet (see {@link org.apache.hadoop.hbase.rsgroup.RSGroupInfo}), 310 * having its contains()'s time complexity as O(logn), which is good enough. 311 * TODO: consider using HashSet to pursue O(1) for contains() throughout the calling chain 312 * if needed. */ 313 ArrayList<ServerName> finalList = new ArrayList<>(); 314 for (ServerName onlineServer : onlineServers) { 315 if (servers.contains(onlineServer.getAddress())) { 316 finalList.add(onlineServer); 317 } 318 } 319 320 return finalList; 321 } 322 323 @VisibleForTesting 324 public Set<RegionInfo> getMisplacedRegions( 325 Map<RegionInfo, ServerName> regions) throws IOException { 326 Set<RegionInfo> misplacedRegions = new HashSet<>(); 327 for(Map.Entry<RegionInfo, ServerName> region : regions.entrySet()) { 328 RegionInfo regionInfo = region.getKey(); 329 ServerName assignedServer = region.getValue(); 330 String groupName = rsGroupInfoManager.getRSGroupOfTable(regionInfo.getTable()); 331 if (groupName == null) { 332 LOG.info("Group not found for table " + regionInfo.getTable() + ", using default"); 333 groupName = RSGroupInfo.DEFAULT_GROUP; 334 } 335 RSGroupInfo info = rsGroupInfoManager.getRSGroup(groupName); 336 if (assignedServer == null) { 337 LOG.debug("There is no assigned server for {}", region); 338 continue; 339 } 340 RSGroupInfo otherInfo = rsGroupInfoManager.getRSGroupOfServer(assignedServer.getAddress()); 341 if (info == null && otherInfo == null) { 342 LOG.warn("Couldn't obtain rs group information for {} on {}", region, assignedServer); 343 continue; 344 } 345 if ((info == null || !info.containsServer(assignedServer.getAddress()))) { 346 LOG.debug("Found misplaced region: " + regionInfo.getRegionNameAsString() + 347 " on server: " + assignedServer + 348 " found in group: " + otherInfo + 349 " outside of group: " + (info == null ? "UNKNOWN" : info.getName())); 350 misplacedRegions.add(regionInfo); 351 } 352 } 353 return misplacedRegions; 354 } 355 356 private ServerName findServerForRegion( 357 Map<ServerName, List<RegionInfo>> existingAssignments, RegionInfo region) { 358 for (Map.Entry<ServerName, List<RegionInfo>> entry : existingAssignments.entrySet()) { 359 if (entry.getValue().contains(region)) { 360 return entry.getKey(); 361 } 362 } 363 364 throw new IllegalStateException("Could not find server for region " 365 + region.getShortNameToLog()); 366 } 367 368 private Map<ServerName, List<RegionInfo>> correctAssignments( 369 Map<ServerName, List<RegionInfo>> existingAssignments) 370 throws HBaseIOException{ 371 Map<ServerName, List<RegionInfo>> correctAssignments = new TreeMap<>(); 372 correctAssignments.put(LoadBalancer.BOGUS_SERVER_NAME, new LinkedList<>()); 373 for (Map.Entry<ServerName, List<RegionInfo>> assignments : existingAssignments.entrySet()){ 374 ServerName sName = assignments.getKey(); 375 correctAssignments.put(sName, new LinkedList<>()); 376 List<RegionInfo> regions = assignments.getValue(); 377 for (RegionInfo region : regions) { 378 RSGroupInfo targetRSGInfo = null; 379 try { 380 String groupName = rsGroupInfoManager.getRSGroupOfTable(region.getTable()); 381 if (groupName == null) { 382 LOG.info("Group not found for table " + region.getTable() + ", using default"); 383 groupName = RSGroupInfo.DEFAULT_GROUP; 384 } 385 targetRSGInfo = rsGroupInfoManager.getRSGroup(groupName); 386 } catch (IOException exp) { 387 LOG.debug("RSGroup information null for region of table " + region.getTable(), 388 exp); 389 } 390 if ((targetRSGInfo == null) || (!targetRSGInfo.containsServer(sName.getAddress()))) { 391 correctAssignments.get(LoadBalancer.BOGUS_SERVER_NAME).add(region); 392 } else { 393 correctAssignments.get(sName).add(region); 394 } 395 } 396 } 397 return correctAssignments; 398 } 399 400 @Override 401 public void initialize() throws HBaseIOException { 402 try { 403 if (rsGroupInfoManager == null) { 404 List<RSGroupAdminEndpoint> cps = 405 masterServices.getMasterCoprocessorHost().findCoprocessors(RSGroupAdminEndpoint.class); 406 if (cps.size() != 1) { 407 String msg = "Expected one implementation of GroupAdminEndpoint but found " + cps.size(); 408 LOG.error(msg); 409 throw new HBaseIOException(msg); 410 } 411 rsGroupInfoManager = cps.get(0).getGroupInfoManager(); 412 if(rsGroupInfoManager == null){ 413 String msg = "RSGroupInfoManager hasn't been initialized"; 414 LOG.error(msg); 415 throw new HBaseIOException(msg); 416 } 417 rsGroupInfoManager.start(); 418 } 419 } catch (IOException e) { 420 throw new HBaseIOException("Failed to initialize GroupInfoManagerImpl", e); 421 } 422 423 // Create the balancer 424 Class<? extends LoadBalancer> balancerKlass = config.getClass(HBASE_RSGROUP_LOADBALANCER_CLASS, 425 StochasticLoadBalancer.class, LoadBalancer.class); 426 internalBalancer = ReflectionUtils.newInstance(balancerKlass, config); 427 internalBalancer.setMasterServices(masterServices); 428 internalBalancer.setClusterMetrics(clusterStatus); 429 internalBalancer.setConf(config); 430 internalBalancer.initialize(); 431 } 432 433 public boolean isOnline() { 434 if (this.rsGroupInfoManager == null) { 435 return false; 436 } 437 438 return this.rsGroupInfoManager.isOnline(); 439 } 440 441 @Override 442 public void setClusterLoad(Map<TableName, Map<ServerName, List<RegionInfo>>> clusterLoad) { 443 } 444 445 @Override 446 public void regionOnline(RegionInfo regionInfo, ServerName sn) { 447 } 448 449 @Override 450 public void regionOffline(RegionInfo regionInfo) { 451 } 452 453 @Override 454 public void onConfigurationChange(Configuration conf) { 455 //DO nothing for now 456 } 457 458 @Override 459 public void stop(String why) { 460 } 461 462 @Override 463 public boolean isStopped() { 464 return false; 465 } 466 467 @VisibleForTesting 468 public void setRsGroupInfoManager(RSGroupInfoManager rsGroupInfoManager) { 469 this.rsGroupInfoManager = rsGroupInfoManager; 470 } 471 472 @Override 473 public void postMasterStartupInitialize() { 474 this.internalBalancer.postMasterStartupInitialize(); 475 } 476 477 public void updateBalancerStatus(boolean status) { 478 internalBalancer.updateBalancerStatus(status); 479 } 480}