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.hbck; 019 020import java.io.IOException; 021import java.time.Instant; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import org.apache.hadoop.fs.FileSystem; 029import org.apache.hadoop.fs.Path; 030import org.apache.hadoop.hbase.CatalogFamilyFormat; 031import org.apache.hadoop.hbase.MetaTableAccessor; 032import org.apache.hadoop.hbase.ScheduledChore; 033import org.apache.hadoop.hbase.ServerName; 034import org.apache.hadoop.hbase.client.RegionInfo; 035import org.apache.hadoop.hbase.client.TableState; 036import org.apache.hadoop.hbase.master.MasterServices; 037import org.apache.hadoop.hbase.master.RegionState; 038import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 039import org.apache.hadoop.hbase.util.FSUtils; 040import org.apache.hadoop.hbase.util.HbckRegionInfo; 041import org.apache.hadoop.hbase.util.Pair; 042import org.apache.yetus.audience.InterfaceAudience; 043import org.apache.yetus.audience.InterfaceStability; 044import org.slf4j.Logger; 045import org.slf4j.LoggerFactory; 046 047/** 048 * Used to do the hbck checking job at master side. 049 */ 050@InterfaceAudience.Private 051@InterfaceStability.Evolving 052public class HbckChore extends ScheduledChore { 053 private static final Logger LOG = LoggerFactory.getLogger(HbckChore.class.getName()); 054 055 private static final String HBCK_CHORE_INTERVAL = "hbase.master.hbck.chore.interval"; 056 private static final int DEFAULT_HBCK_CHORE_INTERVAL = 60 * 60 * 1000; 057 058 private final MasterServices master; 059 060 /** 061 * Saved report from last time this chore ran. Check its date. 062 */ 063 private volatile HbckReport lastReport = null; 064 065 /** 066 * When running, the "snapshot" may be changed when this round's checking finish. 067 */ 068 private volatile boolean running = false; 069 070 private boolean disabled = false; 071 072 public HbckChore(MasterServices master) { 073 super("HbckChore-", master, 074 master.getConfiguration().getInt(HBCK_CHORE_INTERVAL, DEFAULT_HBCK_CHORE_INTERVAL)); 075 this.master = master; 076 int interval = 077 master.getConfiguration().getInt(HBCK_CHORE_INTERVAL, DEFAULT_HBCK_CHORE_INTERVAL); 078 if (interval <= 0) { 079 LOG.warn(HBCK_CHORE_INTERVAL + " is <=0 hence disabling hbck chore"); 080 disableChore(); 081 } 082 } 083 084 /** 085 * Returns Returns last published Report that comes of last successful execution of this chore. 086 */ 087 public HbckReport getLastReport() { 088 return lastReport; 089 } 090 091 @Override 092 protected synchronized void chore() { 093 if (isDisabled() || isRunning()) { 094 LOG.warn("hbckChore is either disabled or is already running. Can't run the chore"); 095 return; 096 } 097 running = true; 098 final HbckReport report = new HbckReport(); 099 report.setCheckingStartTimestamp(Instant.ofEpochMilli(EnvironmentEdgeManager.currentTime())); 100 try { 101 loadRegionsFromInMemoryState(report); 102 loadRegionsFromRSReport(report); 103 try { 104 loadRegionsFromFS(scanForMergedParentRegions(), report); 105 } catch (IOException e) { 106 LOG.warn("Failed to load the regions from filesystem", e); 107 } 108 } catch (Throwable t) { 109 LOG.warn("Unexpected", t); 110 } 111 report.setCheckingEndTimestamp(Instant.ofEpochMilli(EnvironmentEdgeManager.currentTime())); 112 this.lastReport = report; 113 running = false; 114 updateAssignmentManagerMetrics(report); 115 } 116 117 /** 118 * Request execution of this chore's action. 119 * @return {@code true} if the chore was executed, {@code false} if the chore is disabled or 120 * already running. 121 */ 122 public boolean runChore() { 123 // This function does the sanity checks of making sure the chore is not run when it is 124 // disabled or when it's already running. It returns whether the chore was actually run or not. 125 if (isDisabled() || isRunning()) { 126 if (isDisabled()) { 127 LOG.warn("hbck chore is disabled! Set " + HBCK_CHORE_INTERVAL + " > 0 to enable it."); 128 } else { 129 LOG.warn("hbck chore already running. Can't run till it finishes."); 130 } 131 return false; 132 } 133 chore(); 134 return true; 135 } 136 137 private void disableChore() { 138 this.disabled = true; 139 } 140 141 public boolean isDisabled() { 142 return this.disabled; 143 } 144 145 /** 146 * Scan hbase:meta to get set of merged parent regions, this is a very heavy scan. 147 * @return Return generated {@link HashSet} 148 */ 149 private HashSet<String> scanForMergedParentRegions() throws IOException { 150 HashSet<String> mergedParentRegions = new HashSet<>(); 151 // Null tablename means scan all of meta. 152 MetaTableAccessor.scanMetaForTableRegions(this.master.getConnection(), r -> { 153 List<RegionInfo> mergeParents = CatalogFamilyFormat.getMergeRegions(r.rawCells()); 154 if (mergeParents != null) { 155 for (RegionInfo mergeRegion : mergeParents) { 156 if (mergeRegion != null) { 157 // This region is already being merged 158 mergedParentRegions.add(mergeRegion.getEncodedName()); 159 } 160 } 161 } 162 return true; 163 }, null); 164 return mergedParentRegions; 165 } 166 167 private void loadRegionsFromInMemoryState(final HbckReport report) { 168 List<RegionState> regionStates = 169 master.getAssignmentManager().getRegionStates().getRegionStates(); 170 for (RegionState regionState : regionStates) { 171 RegionInfo regionInfo = regionState.getRegion(); 172 if ( 173 master.getTableStateManager().isTableState(regionInfo.getTable(), TableState.State.DISABLED) 174 ) { 175 report.getDisabledTableRegions().add(regionInfo.getRegionNameAsString()); 176 } 177 // Check both state and regioninfo for split status, see HBASE-26383 178 if (regionState.isSplit() || regionInfo.isSplit()) { 179 report.getSplitParentRegions().add(regionInfo.getRegionNameAsString()); 180 } 181 HbckRegionInfo.MetaEntry metaEntry = new HbckRegionInfo.MetaEntry(regionInfo, 182 regionState.getServerName(), regionState.getStamp()); 183 report.getRegionInfoMap().put(regionInfo.getEncodedName(), new HbckRegionInfo(metaEntry)); 184 } 185 LOG.info("Loaded {} regions ({} disabled, {} split parents) from in-memory state", 186 regionStates.size(), report.getDisabledTableRegions().size(), 187 report.getSplitParentRegions().size()); 188 if (LOG.isDebugEnabled()) { 189 Map<RegionState.State, Integer> stateCountMap = new HashMap<>(); 190 for (RegionState regionState : regionStates) { 191 stateCountMap.compute(regionState.getState(), (k, v) -> (v == null) ? 1 : v + 1); 192 } 193 StringBuffer sb = new StringBuffer(); 194 sb.append("Regions by state: "); 195 stateCountMap.entrySet().forEach(e -> { 196 sb.append(e.getKey()); 197 sb.append('='); 198 sb.append(e.getValue()); 199 sb.append(' '); 200 }); 201 LOG.debug(sb.toString()); 202 } 203 if (LOG.isTraceEnabled()) { 204 for (RegionState regionState : regionStates) { 205 LOG.trace("{}: {}, serverName={}", regionState.getRegion(), regionState.getState(), 206 regionState.getServerName()); 207 } 208 } 209 } 210 211 private void loadRegionsFromRSReport(final HbckReport report) { 212 int numRegions = 0; 213 Map<ServerName, Set<byte[]>> rsReports = master.getAssignmentManager().getRSReports(); 214 for (Map.Entry<ServerName, Set<byte[]>> entry : rsReports.entrySet()) { 215 ServerName serverName = entry.getKey(); 216 for (byte[] regionName : entry.getValue()) { 217 String encodedRegionName = RegionInfo.encodeRegionName(regionName); 218 HbckRegionInfo hri = report.getRegionInfoMap().get(encodedRegionName); 219 if (hri == null) { 220 report.getOrphanRegionsOnRS().put(RegionInfo.getRegionNameAsString(regionName), 221 serverName); 222 continue; 223 } 224 hri.addServer(hri.getMetaEntry().getRegionInfo(), serverName); 225 } 226 numRegions += entry.getValue().size(); 227 } 228 LOG.info("Loaded {} regions from {} regionservers' reports and found {} orphan regions", 229 numRegions, rsReports.size(), report.getOrphanRegionsOnRS().size()); 230 231 for (Map.Entry<String, HbckRegionInfo> entry : report.getRegionInfoMap().entrySet()) { 232 HbckRegionInfo hri = entry.getValue(); 233 ServerName locationInMeta = hri.getMetaEntry().getRegionServer(); // can be null 234 if (hri.getDeployedOn().size() == 0) { 235 // skip the offline region which belong to disabled table. 236 if (report.getDisabledTableRegions().contains(hri.getRegionNameAsString())) { 237 continue; 238 } 239 // skip the split parent regions 240 if (report.getSplitParentRegions().contains(hri.getRegionNameAsString())) { 241 continue; 242 } 243 // Master thought this region opened, but no regionserver reported it. 244 report.getInconsistentRegions().put(hri.getRegionNameAsString(), 245 new Pair<>(locationInMeta, new LinkedList<>())); 246 } else if (hri.getDeployedOn().size() > 1) { 247 // More than one regionserver reported opened this region 248 report.getInconsistentRegions().put(hri.getRegionNameAsString(), 249 new Pair<>(locationInMeta, hri.getDeployedOn())); 250 } else if (!hri.getDeployedOn().get(0).equals(locationInMeta)) { 251 // Master thought this region opened on Server1, but regionserver reported Server2 252 report.getInconsistentRegions().put(hri.getRegionNameAsString(), 253 new Pair<>(locationInMeta, hri.getDeployedOn())); 254 } 255 } 256 } 257 258 private void loadRegionsFromFS(final HashSet<String> mergedParentRegions, final HbckReport report) 259 throws IOException { 260 Path rootDir = master.getMasterFileSystem().getRootDir(); 261 FileSystem fs = master.getMasterFileSystem().getFileSystem(); 262 263 int numRegions = 0; 264 List<Path> tableDirs = FSUtils.getTableDirs(fs, rootDir); 265 for (Path tableDir : tableDirs) { 266 List<Path> regionDirs = FSUtils.getRegionDirs(fs, tableDir); 267 for (Path regionDir : regionDirs) { 268 String encodedRegionName = regionDir.getName(); 269 if (encodedRegionName == null) { 270 LOG.warn("Failed get of encoded name from {}", regionDir); 271 continue; 272 } 273 HbckRegionInfo hri = report.getRegionInfoMap().get(encodedRegionName); 274 // If it is not in in-memory database and not a merged region, 275 // report it as an orphan region. 276 if (hri == null && !mergedParentRegions.contains(encodedRegionName)) { 277 report.getOrphanRegionsOnFS().put(encodedRegionName, regionDir); 278 continue; 279 } 280 } 281 numRegions += regionDirs.size(); 282 } 283 LOG.info("Loaded {} tables {} regions from filesystem and found {} orphan regions", 284 tableDirs.size(), numRegions, report.getOrphanRegionsOnFS().size()); 285 } 286 287 private void updateAssignmentManagerMetrics(final HbckReport report) { 288 master.getAssignmentManager().getAssignmentManagerMetrics() 289 .updateOrphanRegionsOnRs(report.getOrphanRegionsOnRS().size()); 290 master.getAssignmentManager().getAssignmentManagerMetrics() 291 .updateOrphanRegionsOnFs(report.getOrphanRegionsOnFS().size()); 292 master.getAssignmentManager().getAssignmentManagerMetrics() 293 .updateInconsistentRegions(report.getInconsistentRegions().size()); 294 } 295 296 /** 297 * When running, the HBCK report may be changed later. 298 */ 299 public boolean isRunning() { 300 return running; 301 } 302}