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 */
018package org.apache.hadoop.hbase.master.balancer;
019
020import java.util.concurrent.ThreadLocalRandom;
021import org.agrona.collections.Int2IntCounterMap;
022import org.agrona.collections.IntArrayList;
023import org.apache.yetus.audience.InterfaceAudience;
024
025/**
026 * Generates candidates which moves the replicas out of the region server for co-hosted region
027 * replicas
028 */
029@InterfaceAudience.Private
030class RegionReplicaCandidateGenerator extends CandidateGenerator {
031
032  protected final RandomCandidateGenerator randomGenerator = new RandomCandidateGenerator();
033
034  /**
035   * Randomly select one regionIndex out of all region replicas co-hosted in the same group (a group
036   * is a server, host or rack)
037   * @param colocatedReplicaCountsPerGroup either Cluster.colocatedReplicaCountsPerServer,
038   *                                       colocatedReplicaCountsPerHost or
039   *                                       colocatedReplicaCountsPerRack
040   * @param regionsPerGroup                either Cluster.regionsPerServer, regionsPerHost or
041   *                                       regionsPerRack
042   * @param regionIndexToPrimaryIndex      Cluster.regionsIndexToPrimaryIndex
043   * @return a regionIndex for the selected primary or -1 if there is no co-locating
044   */
045  int selectCoHostedRegionPerGroup(Int2IntCounterMap colocatedReplicaCountsPerGroup,
046    int[] regionsPerGroup, int[] regionIndexToPrimaryIndex) {
047    final IntArrayList colocated = new IntArrayList(colocatedReplicaCountsPerGroup.size(), -1);
048    colocatedReplicaCountsPerGroup.forEach((primary, count) -> {
049      if (count > 1) { // means consecutive primaries, indicating co-location
050        colocated.add(primary);
051      }
052    });
053
054    if (!colocated.isEmpty()) {
055      int rand = ThreadLocalRandom.current().nextInt(colocated.size());
056      int selectedPrimaryIndex = colocated.get(rand);
057      // we have found the primary id for the region to move. Now find the actual regionIndex
058      // with the given primary, prefer to move the secondary region.
059      for (int regionIndex : regionsPerGroup) {
060        if (selectedPrimaryIndex == regionIndexToPrimaryIndex[regionIndex]) {
061          // always move the secondary, not the primary
062          if (selectedPrimaryIndex != regionIndex) {
063            return regionIndex;
064          }
065        }
066      }
067    }
068    return -1;
069  }
070
071  @Override
072  BalanceAction generate(BalancerClusterState cluster) {
073    int serverIndex = pickRandomServer(cluster);
074    if (cluster.numServers <= 1 || serverIndex == -1) {
075      return BalanceAction.NULL_ACTION;
076    }
077
078    int regionIndex =
079      selectCoHostedRegionPerGroup(cluster.colocatedReplicaCountsPerServer[serverIndex],
080        cluster.regionsPerServer[serverIndex], cluster.regionIndexToPrimaryIndex);
081
082    // if there are no pairs of region replicas co-hosted, default to random generator
083    if (regionIndex == -1) {
084      // default to randompicker
085      return randomGenerator.generate(cluster);
086    }
087
088    int toServerIndex = pickOtherRandomServer(cluster, serverIndex);
089    int toRegionIndex = pickRandomRegion(cluster, toServerIndex, 0.9f);
090    return getAction(serverIndex, regionIndex, toServerIndex, toRegionIndex);
091  }
092}