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.util; 019 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Comparator; 023import java.util.List; 024import java.util.Map.Entry; 025import java.util.TreeMap; 026import java.util.TreeSet; 027import org.apache.hadoop.hbase.util.Bytes.ByteArrayComparator; 028import org.apache.yetus.audience.InterfaceAudience; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032import org.apache.hbase.thirdparty.com.google.common.collect.ArrayListMultimap; 033import org.apache.hbase.thirdparty.com.google.common.collect.Multimap; 034import org.apache.hbase.thirdparty.com.google.common.collect.TreeMultimap; 035 036/** 037 * This is a generic region split calculator. It requires Ranges that provide start, end, and a 038 * comparator. It works in two phases -- the first adds ranges and rejects backwards ranges. Then 039 * one calls calcRegions to generate the multimap that has a start split key as a key and possibly 040 * multiple Ranges as members. To traverse, one normally would get the split set, and iterate 041 * through the calcRegions. Normal regions would have only one entry, holes would have zero, and any 042 * overlaps would have multiple entries. The interface is a bit cumbersome currently but is exposed 043 * this way so that clients can choose how to iterate through the region splits. 044 * @param <R> 045 */ 046@InterfaceAudience.Private 047public class RegionSplitCalculator<R extends KeyRange> { 048 private static final Logger LOG = LoggerFactory.getLogger(RegionSplitCalculator.class); 049 050 private final Comparator<R> rangeCmp; 051 /** 052 * This contains a sorted set of all the possible split points Invariant: once populated this has 053 * 0 entries if empty or at most n+1 values where n == number of added ranges. 054 */ 055 private final TreeSet<byte[]> splits = new TreeSet<>(BYTES_COMPARATOR); 056 057 /** 058 * This is a map from start key to regions with the same start key. Invariant: This always have * 059 * values in total 060 */ 061 private final Multimap<byte[], R> starts = ArrayListMultimap.create(); 062 063 /** 064 * SPECIAL CASE 065 */ 066 private final static byte[] ENDKEY = null; 067 068 public RegionSplitCalculator(Comparator<R> cmp) { 069 rangeCmp = cmp; 070 } 071 072 public final static Comparator<byte[]> BYTES_COMPARATOR = new ByteArrayComparator() { 073 @Override 074 public int compare(byte[] l, byte[] r) { 075 if (l == null && r == null) return 0; 076 if (l == null) return 1; 077 if (r == null) return -1; 078 return super.compare(l, r); 079 } 080 }; 081 082 /** 083 * SPECIAL CASE wrapper for empty end key 084 * @return ENDKEY if end key is empty, else normal endkey. 085 */ 086 private static <R extends KeyRange> byte[] specialEndKey(R range) { 087 byte[] end = range.getEndKey(); 088 if (end.length == 0) { 089 return ENDKEY; 090 } 091 return end; 092 } 093 094 /** 095 * Adds an edge to the split calculator 096 * @return true if is included, false if backwards/invalid 097 */ 098 public boolean add(R range) { 099 byte[] start = range.getStartKey(); 100 byte[] end = specialEndKey(range); 101 102 // No need to use Arrays.equals because ENDKEY is null 103 if (end != ENDKEY && Bytes.compareTo(start, end) > 0) { 104 // don't allow backwards edges 105 LOG.debug( 106 "attempted to add backwards edge: " + Bytes.toString(start) + " " + Bytes.toString(end)); 107 return false; 108 } 109 110 splits.add(start); 111 splits.add(end); 112 starts.put(start, range); 113 return true; 114 } 115 116 /** 117 * Generates a coverage multimap from split key to Regions that start with the split key. 118 * @return coverage multimap 119 */ 120 public Multimap<byte[], R> calcCoverage() { 121 // This needs to be sorted to force the use of the comparator on the values, 122 // otherwise byte array comparison isn't used 123 Multimap<byte[], R> regions = TreeMultimap.create(BYTES_COMPARATOR, rangeCmp); 124 125 // march through all splits from the start points 126 for (Entry<byte[], Collection<R>> start : starts.asMap().entrySet()) { 127 byte[] key = start.getKey(); 128 for (R r : start.getValue()) { 129 regions.put(key, r); 130 131 for (byte[] coveredSplit : splits.subSet(r.getStartKey(), specialEndKey(r))) { 132 regions.put(coveredSplit, r); 133 } 134 } 135 } 136 return regions; 137 } 138 139 public TreeSet<byte[]> getSplits() { 140 return splits; 141 } 142 143 public Multimap<byte[], R> getStarts() { 144 return starts; 145 } 146 147 /** 148 * Find specified number of top ranges in a big overlap group. It could return less if there are 149 * not that many top ranges. Once these top ranges are excluded, the big overlap group will be 150 * broken into ranges with no overlapping, or smaller overlapped groups, and most likely some 151 * holes. 152 * @param bigOverlap a list of ranges that overlap with each other 153 * @param count the max number of ranges to find 154 * @return a list of ranges that overlap with most others 155 */ 156 public static <R extends KeyRange> List<R> findBigRanges(Collection<R> bigOverlap, int count) { 157 List<R> bigRanges = new ArrayList<>(); 158 159 // The key is the count of overlaps, 160 // The value is a list of ranges that have that many overlaps 161 TreeMap<Integer, List<R>> overlapRangeMap = new TreeMap<>(); 162 for (R r : bigOverlap) { 163 // Calculates the # of overlaps for each region 164 // and populates rangeOverlapMap 165 byte[] startKey = r.getStartKey(); 166 byte[] endKey = specialEndKey(r); 167 168 int overlappedRegions = 0; 169 for (R rr : bigOverlap) { 170 byte[] start = rr.getStartKey(); 171 byte[] end = specialEndKey(rr); 172 173 if ( 174 BYTES_COMPARATOR.compare(startKey, end) < 0 && BYTES_COMPARATOR.compare(endKey, start) > 0 175 ) { 176 overlappedRegions++; 177 } 178 } 179 180 // One region always overlaps with itself, 181 // so overlappedRegions should be more than 1 182 // for actual overlaps. 183 if (overlappedRegions > 1) { 184 Integer key = Integer.valueOf(overlappedRegions); 185 List<R> ranges = overlapRangeMap.get(key); 186 if (ranges == null) { 187 ranges = new ArrayList<>(); 188 overlapRangeMap.put(key, ranges); 189 } 190 ranges.add(r); 191 } 192 } 193 int toBeAdded = count; 194 for (Integer key : overlapRangeMap.descendingKeySet()) { 195 List<R> chunk = overlapRangeMap.get(key); 196 int chunkSize = chunk.size(); 197 if (chunkSize <= toBeAdded) { 198 bigRanges.addAll(chunk); 199 toBeAdded -= chunkSize; 200 if (toBeAdded > 0) continue; 201 } else { 202 // Try to use the middle chunk in case the overlapping is 203 // chained, for example: [a, c), [b, e), [d, g), [f h)... 204 // In such a case, sideline the middle chunk will break 205 // the group efficiently. 206 int start = (chunkSize - toBeAdded) / 2; 207 int end = start + toBeAdded; 208 for (int i = start; i < end; i++) { 209 bigRanges.add(chunk.get(i)); 210 } 211 } 212 break; 213 } 214 return bigRanges; 215 } 216}