View Javadoc

1   /**
2    * Copyright The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.rsgroup;
22  
23  import com.google.common.collect.ArrayListMultimap;
24  import com.google.common.collect.LinkedListMultimap;
25  import com.google.common.collect.ListMultimap;
26  import com.google.common.collect.Lists;
27  import com.google.common.collect.Maps;
28  
29  import java.io.IOException;
30  import java.util.ArrayList;
31  import java.util.Collections;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.LinkedList;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.TreeMap;
39  
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  import org.apache.hadoop.conf.Configuration;
43  import org.apache.hadoop.hbase.ClusterStatus;
44  import org.apache.hadoop.hbase.HBaseIOException;
45  import org.apache.hadoop.hbase.HRegionInfo;
46  import org.apache.hadoop.hbase.ServerName;
47  import org.apache.hadoop.hbase.TableName;
48  import org.apache.hadoop.hbase.classification.InterfaceAudience;
49  import org.apache.hadoop.hbase.constraint.ConstraintException;
50  import org.apache.hadoop.hbase.master.LoadBalancer;
51  import org.apache.hadoop.hbase.master.MasterServices;
52  import org.apache.hadoop.hbase.master.RegionPlan;
53  import org.apache.hadoop.hbase.master.balancer.StochasticLoadBalancer;
54  import org.apache.hadoop.hbase.net.Address;
55  import org.apache.hadoop.util.ReflectionUtils;
56  
57  /**
58   * GroupBasedLoadBalancer, used when Region Server Grouping is configured (HBase-6721)
59   * It does region balance based on a table's group membership.
60   *
61   * Most assignment methods contain two exclusive code paths: Online - when the group
62   * table is online and Offline - when it is unavailable.
63   *
64   * During Offline, assignments are assigned based on cached information in zookeeper.
65   * If unavailable (ie bootstrap) then regions are assigned randomly.
66   *
67   * Once the GROUP table has been assigned, the balancer switches to Online and will then
68   * start providing appropriate assignments for user tables.
69   *
70   */
71  @InterfaceAudience.Private
72  public class RSGroupBasedLoadBalancer implements RSGroupableBalancer, LoadBalancer {
73    /** Config for pluggable load balancers */
74    public static final String HBASE_GROUP_LOADBALANCER_CLASS = "hbase.group.grouploadbalancer.class";
75  
76    private static final Log LOG = LogFactory.getLog(RSGroupBasedLoadBalancer.class);
77  
78    private Configuration config;
79    private ClusterStatus clusterStatus;
80    private MasterServices masterServices;
81    private RSGroupInfoManager infoManager;
82    private LoadBalancer internalBalancer;
83  
84    //used during reflection by LoadBalancerFactory
85    @InterfaceAudience.Private
86    public RSGroupBasedLoadBalancer() {
87    }
88  
89    //This constructor should only be used for unit testing
90    @InterfaceAudience.Private
91    public RSGroupBasedLoadBalancer(RSGroupInfoManager RSGroupInfoManager) {
92      this.infoManager = RSGroupInfoManager;
93    }
94  
95    @Override
96    public Configuration getConf() {
97      return config;
98    }
99  
100   @Override
101   public void setConf(Configuration conf) {
102     this.config = conf;
103     if (internalBalancer != null) {
104       internalBalancer.setConf(conf);
105     }
106   }
107 
108   @Override
109   public void setClusterStatus(ClusterStatus st) {
110     this.clusterStatus = st;
111     if (internalBalancer != null) {
112       internalBalancer.setClusterStatus(st);
113     }
114   }
115 
116   @Override
117   public void setMasterServices(MasterServices masterServices) {
118     this.masterServices = masterServices;
119   }
120 
121   @Override
122   public List<RegionPlan> balanceCluster(TableName tableName, Map<ServerName, List<HRegionInfo>>
123       clusterState) throws HBaseIOException {
124     return balanceCluster(clusterState);
125   }
126 
127   @Override
128   public List<RegionPlan> balanceCluster(Map<ServerName, List<HRegionInfo>> clusterState)
129       throws HBaseIOException {
130     if (!isOnline()) {
131       throw new ConstraintException(RSGroupInfoManager.RSGROUP_TABLE_NAME +
132           " is not online, unable to perform balance");
133     }
134 
135     Map<ServerName,List<HRegionInfo>> correctedState = correctAssignments(clusterState);
136     List<RegionPlan> regionPlans = new ArrayList<RegionPlan>();
137 
138     List<HRegionInfo> misplacedRegions = correctedState.get(LoadBalancer.BOGUS_SERVER_NAME);
139     for (HRegionInfo regionInfo : misplacedRegions) {
140       regionPlans.add(new RegionPlan(regionInfo, null, null));
141     }
142     try {
143       // Record which region servers have been processed,so as to skip them after processed
144       HashSet<ServerName> processedServers = new HashSet<>();
145 
146       // For each rsgroup
147       for (RSGroupInfo rsgroup : infoManager.listRSGroups()) {
148         Map<ServerName, List<HRegionInfo>> groupClusterState = new HashMap<>();
149         for (ServerName server : clusterState.keySet()) { // for each region server
150           if (!processedServers.contains(server) // server is not processed yet
151               && rsgroup.containsServer(server.getAddress())) { // server belongs to this rsgroup
152             List<HRegionInfo> regionsOnServer = correctedState.get(server);
153             groupClusterState.put(server, regionsOnServer);
154             processedServers.add(server);
155           }
156         }
157 
158         List<RegionPlan> groupPlans = this.internalBalancer
159             .balanceCluster(groupClusterState);
160         if (groupPlans != null) {
161           regionPlans.addAll(groupPlans);
162         }
163       }
164     } catch (IOException exp) {
165       LOG.warn("Exception while balancing cluster.", exp);
166       regionPlans.clear();
167     }
168     return regionPlans;
169   }
170 
171   @Override
172   public Map<ServerName, List<HRegionInfo>> roundRobinAssignment(
173       List<HRegionInfo> regions, List<ServerName> servers) throws HBaseIOException {
174     Map<ServerName, List<HRegionInfo>> assignments = Maps.newHashMap();
175     ListMultimap<String,HRegionInfo> regionMap = ArrayListMultimap.create();
176     ListMultimap<String,ServerName> serverMap = ArrayListMultimap.create();
177     generateGroupMaps(regions, servers, regionMap, serverMap);
178     for(String groupKey : regionMap.keySet()) {
179       if (regionMap.get(groupKey).size() > 0) {
180         Map<ServerName, List<HRegionInfo>> result =
181             this.internalBalancer.roundRobinAssignment(
182                 regionMap.get(groupKey),
183                 serverMap.get(groupKey));
184         if(result != null) {
185           if(result.containsKey(LoadBalancer.BOGUS_SERVER_NAME) &&
186               assignments.containsKey(LoadBalancer.BOGUS_SERVER_NAME)){
187             assignments.get(LoadBalancer.BOGUS_SERVER_NAME).addAll(
188               result.get(LoadBalancer.BOGUS_SERVER_NAME));
189           } else {
190             assignments.putAll(result);
191           }
192         }
193       }
194     }
195     return assignments;
196   }
197 
198   @Override
199   public Map<ServerName, List<HRegionInfo>> retainAssignment(
200       Map<HRegionInfo, ServerName> regions, List<ServerName> servers) throws HBaseIOException {
201     try {
202       Map<ServerName, List<HRegionInfo>> assignments = new TreeMap<ServerName, List<HRegionInfo>>();
203       ListMultimap<String, HRegionInfo> groupToRegion = ArrayListMultimap.create();
204       Set<HRegionInfo> misplacedRegions = getMisplacedRegions(regions);
205       for (HRegionInfo region : regions.keySet()) {
206         if (!misplacedRegions.contains(region)) {
207           String groupName = infoManager.getRSGroupOfTable(region.getTable());
208           if (groupName == null) {
209             LOG.debug("Group not found for table " + region.getTable() + ", using default");
210             groupName = RSGroupInfo.DEFAULT_GROUP;
211           }
212           groupToRegion.put(groupName, region);
213         }
214       }
215       // Now the "groupToRegion" map has only the regions which have correct
216       // assignments.
217       for (String key : groupToRegion.keySet()) {
218         Map<HRegionInfo, ServerName> currentAssignmentMap = new TreeMap<HRegionInfo, ServerName>();
219         List<HRegionInfo> regionList = groupToRegion.get(key);
220         RSGroupInfo info = infoManager.getRSGroup(key);
221         List<ServerName> candidateList = filterOfflineServers(info, servers);
222         for (HRegionInfo region : regionList) {
223           currentAssignmentMap.put(region, regions.get(region));
224         }
225         if(candidateList.size() > 0) {
226           assignments.putAll(this.internalBalancer.retainAssignment(
227               currentAssignmentMap, candidateList));
228         }
229       }
230 
231       for (HRegionInfo region : misplacedRegions) {
232         String groupName = infoManager.getRSGroupOfTable(region.getTable());
233         if (groupName == null) {
234           LOG.debug("Group not found for table " + region.getTable() + ", using default");
235           groupName = RSGroupInfo.DEFAULT_GROUP;
236         }
237         RSGroupInfo info = infoManager.getRSGroup(groupName);
238         List<ServerName> candidateList = filterOfflineServers(info, servers);
239         ServerName server = this.internalBalancer.randomAssignment(region,
240             candidateList);
241         if (server != null) {
242           if (!assignments.containsKey(server)) {
243             assignments.put(server, new ArrayList<HRegionInfo>());
244           }
245           assignments.get(server).add(region);
246         } else {
247           //if not server is available assign to bogus so it ends up in RIT
248           if(!assignments.containsKey(LoadBalancer.BOGUS_SERVER_NAME)) {
249             assignments.put(LoadBalancer.BOGUS_SERVER_NAME, new ArrayList<HRegionInfo>());
250           }
251           assignments.get(LoadBalancer.BOGUS_SERVER_NAME).add(region);
252         }
253       }
254       return assignments;
255     } catch (IOException e) {
256       throw new HBaseIOException("Failed to do online retain assignment", e);
257     }
258   }
259 
260   @Override
261   public Map<HRegionInfo, ServerName> immediateAssignment(List<HRegionInfo> regions,
262       List<ServerName> servers) throws HBaseIOException {
263     throw new UnsupportedOperationException("immediateAssignment is not supported");
264   }
265 
266   @Override
267   public ServerName randomAssignment(HRegionInfo region,
268       List<ServerName> servers) throws HBaseIOException {
269     ListMultimap<String,HRegionInfo> regionMap = LinkedListMultimap.create();
270     ListMultimap<String,ServerName> serverMap = LinkedListMultimap.create();
271     generateGroupMaps(Lists.newArrayList(region), servers, regionMap, serverMap);
272     List<ServerName> filteredServers = serverMap.get(regionMap.keySet().iterator().next());
273     return this.internalBalancer.randomAssignment(region, filteredServers);
274   }
275 
276   private void generateGroupMaps(
277     List<HRegionInfo> regions,
278     List<ServerName> servers,
279     ListMultimap<String, HRegionInfo> regionMap,
280     ListMultimap<String, ServerName> serverMap) throws HBaseIOException {
281     try {
282       for (HRegionInfo region : regions) {
283         String groupName = infoManager.getRSGroupOfTable(region.getTable());
284         if (groupName == null) {
285           LOG.debug("Group not found for table " + region.getTable() + ", using default");
286           groupName = RSGroupInfo.DEFAULT_GROUP;
287         }
288         regionMap.put(groupName, region);
289       }
290       for (String groupKey : regionMap.keySet()) {
291         RSGroupInfo info = infoManager.getRSGroup(groupKey);
292         serverMap.putAll(groupKey, filterOfflineServers(info, servers));
293         if(serverMap.get(groupKey).size() < 1) {
294           serverMap.put(groupKey, LoadBalancer.BOGUS_SERVER_NAME);
295         }
296       }
297     } catch(IOException e) {
298       throw new HBaseIOException("Failed to generate group maps", e);
299     }
300   }
301 
302   private List<ServerName> filterOfflineServers(RSGroupInfo RSGroupInfo,
303                                                 List<ServerName> onlineServers) {
304     if (RSGroupInfo != null) {
305       return filterServers(RSGroupInfo.getServers(), onlineServers);
306     } else {
307       LOG.debug("Group Information found to be null. Some regions might be unassigned.");
308       return Collections.emptyList();
309     }
310   }
311 
312   /**
313    * Filter servers based on the online servers.
314    *
315    * @param servers
316    *          the servers
317    * @param onlineServers
318    *          List of servers which are online.
319    * @return the list
320    */
321   private List<ServerName> filterServers(Set<Address> servers,
322       List<ServerName> onlineServers) {
323     /**
324      * servers is actually a TreeSet (see {@link org.apache.hadoop.hbase.rsgroup.RSGroupInfo}),
325      * having its contains()'s time complexity as O(logn), which is good enough.
326      * TODO: consider using HashSet to pursue O(1) for contains() throughout the calling chain
327      * if needed.
328      */
329     ArrayList<ServerName> finalList = new ArrayList<>();
330     for (ServerName onlineServer : onlineServers) {
331       if (servers.contains(onlineServer.getAddress())) {
332         finalList.add(onlineServer);
333       }
334     }
335 
336     return finalList;
337   }
338 
339   public Set<HRegionInfo> getMisplacedRegions(
340       Map<HRegionInfo, ServerName> regions) throws IOException {
341     Set<HRegionInfo> misplacedRegions = new HashSet<HRegionInfo>();
342     for(Map.Entry<HRegionInfo, ServerName> region : regions.entrySet()) {
343       HRegionInfo regionInfo = region.getKey();
344       ServerName assignedServer = region.getValue();
345       String groupName = infoManager.getRSGroupOfTable(regionInfo.getTable());
346       if (groupName == null) {
347         LOG.debug("Group not found for table " + regionInfo.getTable() + ", using default");
348         groupName = RSGroupInfo.DEFAULT_GROUP;
349       }
350       RSGroupInfo info = infoManager.getRSGroup(groupName);
351       if (assignedServer == null) {
352         LOG.debug("There is no assigned server for " + region);
353         continue;
354       }
355       RSGroupInfo otherInfo = infoManager.getRSGroupOfServer(assignedServer.getAddress());
356       if (info == null && otherInfo == null) {
357         LOG.warn("Couldn't obtain rs group information for " + region + " on " + assignedServer);
358         continue;
359       }
360       if ((info == null || !info.containsServer(assignedServer.getAddress()))) {
361         LOG.debug("Found misplaced region: " + regionInfo.getRegionNameAsString() +
362             " on server: " + assignedServer +
363             " found in group: " +  otherInfo +
364             " outside of group: " + (info == null ? "UNKNOWN" : info.getName()));
365         misplacedRegions.add(regionInfo);
366       }
367     }
368     return misplacedRegions;
369   }
370 
371   private Map<ServerName, List<HRegionInfo>> correctAssignments(
372       Map<ServerName, List<HRegionInfo>> existingAssignments) {
373     Map<ServerName, List<HRegionInfo>> correctAssignments =
374         new TreeMap<ServerName, List<HRegionInfo>>();
375     correctAssignments.put(LoadBalancer.BOGUS_SERVER_NAME, new LinkedList<HRegionInfo>());
376     for (Map.Entry<ServerName, List<HRegionInfo>> assignments : existingAssignments.entrySet()){
377       ServerName sName = assignments.getKey();
378       correctAssignments.put(sName, new LinkedList<HRegionInfo>());
379       List<HRegionInfo> regions = assignments.getValue();
380       for (HRegionInfo region : regions) {
381         RSGroupInfo info = null;
382         try {
383           String groupName = infoManager.getRSGroupOfTable(region.getTable());
384           if (groupName == null) {
385             LOG.debug("Group not found for table " + region.getTable() + ", using default");
386             groupName = RSGroupInfo.DEFAULT_GROUP;
387           }
388           info = infoManager.getRSGroup(groupName);
389         } catch (IOException exp) {
390           LOG.debug("Group information null for region of table " + region.getTable(),
391               exp);
392         }
393         if ((info == null) || (!info.containsServer(sName.getAddress()))) {
394           correctAssignments.get(LoadBalancer.BOGUS_SERVER_NAME).add(region);
395         } else {
396           correctAssignments.get(sName).add(region);
397         }
398       }
399     }
400     return correctAssignments;
401   }
402 
403   @Override
404   public void initialize() throws HBaseIOException {
405     try {
406       if (infoManager == null) {
407         List<RSGroupAdminEndpoint> cps =
408           masterServices.getMasterCoprocessorHost().findCoprocessors(RSGroupAdminEndpoint.class);
409         if (cps.size() != 1) {
410           String msg = "Expected one implementation of GroupAdminEndpoint but found " + cps.size();
411           LOG.error(msg);
412           throw new HBaseIOException(msg);
413         }
414         infoManager = cps.get(0).getGroupInfoManager();
415         if(infoManager == null){
416           String msg = "RSGroupInfoManager hasn't been initialized";
417           LOG.error(msg);
418           throw new HBaseIOException(msg);
419         }
420         infoManager.start();
421       }
422     } catch (IOException e) {
423       throw new HBaseIOException("Failed to initialize GroupInfoManagerImpl", e);
424     }
425 
426     // Create the balancer
427     Class<? extends LoadBalancer> balancerKlass = config.getClass(
428         HBASE_GROUP_LOADBALANCER_CLASS,
429         StochasticLoadBalancer.class, LoadBalancer.class);
430     internalBalancer = ReflectionUtils.newInstance(balancerKlass, config);
431     if (clusterStatus != null) {
432       internalBalancer.setClusterStatus(clusterStatus);
433     }
434     internalBalancer.setMasterServices(masterServices);
435     internalBalancer.setConf(config);
436     internalBalancer.initialize();
437   }
438 
439   public boolean isOnline() {
440     return infoManager != null && infoManager.isOnline();
441   }
442 
443   @Override
444   public void regionOnline(HRegionInfo regionInfo, ServerName sn) {
445   }
446 
447   @Override
448   public void regionOffline(HRegionInfo regionInfo) {
449   }
450 
451   @Override
452   public void onConfigurationChange(Configuration conf) {
453     internalBalancer.onConfigurationChange(conf);
454   }
455 
456   @Override
457   public void stop(String why) {
458   }
459 
460   @Override
461   public boolean isStopped() {
462     return false;
463   }
464 
465   @Override
466   public void postMasterStartupInitialize() {
467     this.internalBalancer.postMasterStartupInitialize();
468   }
469 
470   public void updateBalancerStatus(boolean status) {
471     internalBalancer.updateBalancerStatus(status);
472   }
473 }