All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.hadoop.hbase.master.assignment.TransitRegionStateProcedure Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta-1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hadoop.hbase.master.assignment;

import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.IOException;
import org.apache.hadoop.hbase.HBaseIOException;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.RegionReplicaUtil;
import org.apache.hadoop.hbase.client.RetriesExhaustedException;
import org.apache.hadoop.hbase.master.MetricsAssignmentManager;
import org.apache.hadoop.hbase.master.RegionState.State;
import org.apache.hadoop.hbase.master.procedure.AbstractStateMachineRegionProcedure;
import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure;
import org.apache.hadoop.hbase.procedure2.Procedure;
import org.apache.hadoop.hbase.procedure2.ProcedureMetrics;
import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
import org.apache.hadoop.hbase.procedure2.ProcedureSuspendedException;
import org.apache.hadoop.hbase.procedure2.ProcedureUtil;
import org.apache.hadoop.hbase.procedure2.ProcedureYieldException;
import org.apache.hadoop.hbase.util.RetryCounter;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.RegionStateTransitionState;
import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.RegionStateTransitionStateData;
import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.RegionTransitionType;
import org.apache.hadoop.hbase.shaded.protobuf.generated.ProcedureProtos;
import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.RegionStateTransition.TransitionCode;

/**
 * The procedure to deal with the state transition of a region. A region with a TRSP in place is
 * called RIT, i.e, RegionInTransition.
 * 

* It can be used to assign/unassign/reopen/move a region, and for * {@link #unassign(MasterProcedureEnv, RegionInfo)} and * {@link #reopen(MasterProcedureEnv, RegionInfo)}, you do not need to specify a target server, and * for {@link #assign(MasterProcedureEnv, RegionInfo, ServerName)} and * {@link #move(MasterProcedureEnv, RegionInfo, ServerName)}, if you want to you can provide a * target server. And for {@link #move(MasterProcedureEnv, RegionInfo, ServerName)}, if you do not * specify a targetServer, we will select one randomly. *

*

* The typical state transition for assigning a region is: * *

 * GET_ASSIGN_CANDIDATE ------> OPEN -----> CONFIRM_OPENED
 * 
* * Notice that, if there are failures we may go back to the {@code GET_ASSIGN_CANDIDATE} state to * try again. *

* The typical state transition for unassigning a region is: * *

 * CLOSE -----> CONFIRM_CLOSED
 * 
* * Here things go a bit different, if there are failures, especially that if there is a server * crash, we will go to the {@code GET_ASSIGN_CANDIDATE} state to bring the region online first, and * then go through the normal way to unassign it. *

* The typical state transition for reopening/moving a region is: * *

 * CLOSE -----> CONFIRM_CLOSED -----> GET_ASSIGN_CANDIDATE ------> OPEN -----> CONFIRM_OPENED
 * 
* * The retry logic is the same with the above assign/unassign. *

* Notice that, although we allow specify a target server, it just acts as a candidate, we do not * guarantee that the region will finally be on the target server. If this is important for you, you * should check whether the region is on the target server after the procedure is finished. *

* When you want to schedule a TRSP, please check whether there is still one for this region, and * the check should be under the RegionStateNode lock. We will remove the TRSP from a * RegionStateNode when we are done, see the code in {@code reportTransition} method below. There * could be at most one TRSP for a give region. */ @InterfaceAudience.Private public class TransitRegionStateProcedure extends AbstractStateMachineRegionProcedure { private static final Logger LOG = LoggerFactory.getLogger(TransitRegionStateProcedure.class); private TransitionType type; private RegionStateTransitionState initialState; private RegionStateTransitionState lastState; // the candidate where we want to assign the region to. private ServerName assignCandidate; private boolean forceNewPlan; private RetryCounter retryCounter; private RegionRemoteProcedureBase remoteProc; public TransitRegionStateProcedure() { } private void setInitialAndLastState() { switch (type) { case ASSIGN: initialState = RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE; lastState = RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_OPENED; break; case UNASSIGN: initialState = RegionStateTransitionState.REGION_STATE_TRANSITION_CLOSE; lastState = RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_CLOSED; break; case MOVE: case REOPEN: initialState = RegionStateTransitionState.REGION_STATE_TRANSITION_CLOSE; lastState = RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_OPENED; break; default: throw new IllegalArgumentException("Unknown TransitionType: " + type); } } protected TransitRegionStateProcedure(MasterProcedureEnv env, RegionInfo hri, ServerName assignCandidate, boolean forceNewPlan, TransitionType type) { super(env, hri); this.assignCandidate = assignCandidate; this.forceNewPlan = forceNewPlan; this.type = type; setInitialAndLastState(); // when do reopen TRSP, let the rs know the targetServer so it can keep some info on close if (type == TransitionType.REOPEN) { this.assignCandidate = getRegionStateNode(env).getRegionLocation(); } } @Override public TableOperationType getTableOperationType() { // TODO: maybe we should make another type here, REGION_TRANSITION? return TableOperationType.REGION_EDIT; } @Override protected boolean waitInitialized(MasterProcedureEnv env) { if (TableName.isMetaTableName(getTableName())) { return false; } // First we need meta to be loaded, and second, if meta is not online then we will likely to // fail when updating meta so we wait until it is assigned. AssignmentManager am = env.getAssignmentManager(); return am.waitMetaLoaded(this) || am.waitMetaAssigned(this, getRegion()); } private void queueAssign(MasterProcedureEnv env, RegionStateNode regionNode) throws ProcedureSuspendedException { boolean retain = false; if (forceNewPlan) { // set the region location to null if forceNewPlan is true regionNode.setRegionLocation(null); } else { if (assignCandidate != null) { retain = assignCandidate.equals(regionNode.getLastHost()); regionNode.setRegionLocation(assignCandidate); } else if (regionNode.getLastHost() != null) { retain = true; LOG.info("Setting lastHost as the region location {}", regionNode.getLastHost()); regionNode.setRegionLocation(regionNode.getLastHost()); } } LOG.info("Starting {}; {}; forceNewPlan={}, retain={}", this, regionNode.toShortString(), forceNewPlan, retain); env.getAssignmentManager().queueAssign(regionNode); setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_OPEN); if (regionNode.getProcedureEvent().suspendIfNotReady(this)) { throw new ProcedureSuspendedException(); } } private void openRegion(MasterProcedureEnv env, RegionStateNode regionNode) throws IOException { ServerName loc = regionNode.getRegionLocation(); if (loc == null) { LOG.warn("No location specified for {}, jump back to state {} to get one", getRegion(), RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE); setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE); return; } env.getAssignmentManager().regionOpening(regionNode); addChildProcedure(new OpenRegionProcedure(this, getRegion(), loc)); setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_OPENED); } private Flow confirmOpened(MasterProcedureEnv env, RegionStateNode regionNode) throws IOException { if (regionNode.isInState(State.OPEN)) { retryCounter = null; if (lastState == RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_OPENED) { // we are the last state, finish regionNode.unsetProcedure(this); ServerCrashProcedure.updateProgress(env, getParentProcId()); return Flow.NO_MORE_STATE; } // It is possible that we arrive here but confirm opened is not the last state, for example, // when merging or splitting a region, we unassign the region from a RS and the RS is crashed, // then there will be recovered edits for this region, we'd better make the region online // again and then unassign it, otherwise we have to fail the merge/split procedure as we may // loss data. setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_CLOSE); return Flow.HAS_MORE_STATE; } int retries = env.getAssignmentManager().getRegionStates().addToFailedOpen(regionNode) .incrementAndGetRetries(); int maxAttempts = env.getAssignmentManager().getAssignMaxAttempts(); LOG.info("Retry={} of max={}; {}; {}", retries, maxAttempts, this, regionNode.toShortString()); if (retries >= maxAttempts) { env.getAssignmentManager().regionFailedOpen(regionNode, true); setFailure(getClass().getSimpleName(), new RetriesExhaustedException( "Max attempts " + env.getAssignmentManager().getAssignMaxAttempts() + " exceeded")); regionNode.unsetProcedure(this); return Flow.NO_MORE_STATE; } env.getAssignmentManager().regionFailedOpen(regionNode, false); // we failed to assign the region, force a new plan forceNewPlan = true; regionNode.setRegionLocation(null); setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE); if (retries > env.getAssignmentManager().getAssignRetryImmediatelyMaxAttempts()) { // Throw exception to backoff and retry when failed open too many times throw new HBaseIOException("Failed confirm OPEN of " + regionNode + " (remote log may yield more detail on why)."); } else { // Here we do not throw exception because we want to the region to be online ASAP return Flow.HAS_MORE_STATE; } } private void closeRegion(MasterProcedureEnv env, RegionStateNode regionNode) throws IOException { if (regionNode.isInState(State.OPEN, State.CLOSING, State.MERGING, State.SPLITTING)) { // this is the normal case env.getAssignmentManager().regionClosing(regionNode); addChildProcedure(new CloseRegionProcedure(this, getRegion(), regionNode.getRegionLocation(), assignCandidate)); setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_CLOSED); } else { forceNewPlan = true; regionNode.setRegionLocation(null); setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE); } } private Flow confirmClosed(MasterProcedureEnv env, RegionStateNode regionNode) throws IOException { if (regionNode.isInState(State.CLOSED)) { retryCounter = null; if (lastState == RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_CLOSED) { // we are the last state, finish regionNode.unsetProcedure(this); return Flow.NO_MORE_STATE; } // This means we need to open the region again, should be a move or reopen setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE); return Flow.HAS_MORE_STATE; } if (regionNode.isInState(State.CLOSING)) { // This is possible, think the target RS crashes and restarts immediately, the close region // operation will return a NotServingRegionException soon, we can only recover after SCP takes // care of this RS. So here we throw an IOException to let upper layer to retry with backoff. setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_CLOSE); throw new HBaseIOException("Failed to close region"); } // abnormally closed, need to reopen it, no matter what is the last state, see the comment in // confirmOpened for more details that why we need to reopen the region first even if we just // want to close it. // The only exception is for non-default replica, where we do not need to deal with recovered // edits. Notice that the region will remain in ABNORMALLY_CLOSED state, the upper layer need to // deal with this state. For non-default replica, this is usually the same with CLOSED. assert regionNode.isInState(State.ABNORMALLY_CLOSED); if (!RegionReplicaUtil.isDefaultReplica(getRegion()) && lastState == RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_CLOSED) { regionNode.unsetProcedure(this); return Flow.NO_MORE_STATE; } retryCounter = null; setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE); return Flow.HAS_MORE_STATE; } // Override to lock RegionStateNode @SuppressWarnings("rawtypes") @Override protected Procedure[] execute(MasterProcedureEnv env) throws ProcedureSuspendedException, ProcedureYieldException, InterruptedException { RegionStateNode regionNode = env.getAssignmentManager().getRegionStates().getOrCreateRegionStateNode(getRegion()); regionNode.lock(); try { return super.execute(env); } finally { regionNode.unlock(); } } private RegionStateNode getRegionStateNode(MasterProcedureEnv env) { return env.getAssignmentManager().getRegionStates().getOrCreateRegionStateNode(getRegion()); } @Override protected Flow executeFromState(MasterProcedureEnv env, RegionStateTransitionState state) throws ProcedureSuspendedException, ProcedureYieldException, InterruptedException { RegionStateNode regionNode = getRegionStateNode(env); try { switch (state) { case REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE: // Need to do some sanity check for replica region, if the region does not exist at // master, do not try to assign the replica region, log error and return. if (!RegionReplicaUtil.isDefaultReplica(regionNode.getRegionInfo())) { RegionInfo defaultRI = RegionReplicaUtil.getRegionInfoForDefaultReplica(regionNode.getRegionInfo()); if (env.getMasterServices().getAssignmentManager().getRegionStates(). getRegionStateNode(defaultRI) == null) { LOG.error( "Cannot assign replica region {} because its primary region {} does not exist.", regionNode.getRegionInfo(), defaultRI); regionNode.unsetProcedure(this); return Flow.NO_MORE_STATE; } } queueAssign(env, regionNode); return Flow.HAS_MORE_STATE; case REGION_STATE_TRANSITION_OPEN: openRegion(env, regionNode); return Flow.HAS_MORE_STATE; case REGION_STATE_TRANSITION_CONFIRM_OPENED: return confirmOpened(env, regionNode); case REGION_STATE_TRANSITION_CLOSE: closeRegion(env, regionNode); return Flow.HAS_MORE_STATE; case REGION_STATE_TRANSITION_CONFIRM_CLOSED: return confirmClosed(env, regionNode); default: throw new UnsupportedOperationException("unhandled state=" + state); } } catch (IOException e) { if (retryCounter == null) { retryCounter = ProcedureUtil.createRetryCounter(env.getMasterConfiguration()); } long backoff = retryCounter.getBackoffTimeAndIncrementAttempts(); LOG.warn( "Failed transition, suspend {}secs {}; {}; waiting on rectified condition fixed " + "by other Procedure or operator intervention", backoff / 1000, this, regionNode.toShortString(), e); setTimeout(Math.toIntExact(backoff)); setState(ProcedureProtos.ProcedureState.WAITING_TIMEOUT); skipPersistence(); throw new ProcedureSuspendedException(); } } /** * At end of timeout, wake ourselves up so we run again. */ @Override protected synchronized boolean setTimeoutFailure(MasterProcedureEnv env) { setState(ProcedureProtos.ProcedureState.RUNNABLE); env.getProcedureScheduler().addFront(this); return false; // 'false' means that this procedure handled the timeout } // Should be called with RegionStateNode locked public void reportTransition(MasterProcedureEnv env, RegionStateNode regionNode, ServerName serverName, TransitionCode code, long seqId, long procId) throws IOException { if (remoteProc == null) { LOG.warn( "There is no outstanding remote region procedure for {}, serverName={}, code={}," + " seqId={}, proc={}, should be a retry, ignore", regionNode, serverName, code, seqId, this); return; } // The procId could be -1 if it is from an old region server, we need to deal with it so that we // can do rolling upgraing. if (procId >= 0 && remoteProc.getProcId() != procId) { LOG.warn( "The pid of remote region procedure for {} is {}, the reported pid={}, serverName={}," + " code={}, seqId={}, proc={}, should be a retry, ignore", regionNode, remoteProc.getProcId(), procId, serverName, code, seqId, this); return; } remoteProc.reportTransition(env, regionNode, serverName, code, seqId); } // Should be called with RegionStateNode locked public void serverCrashed(MasterProcedureEnv env, RegionStateNode regionNode, ServerName serverName) throws IOException { // force to assign to a new candidate server // AssignmentManager#regionClosedAbnormally will set region location to null // TODO: the forceNewPlan flag not be persistent so if master crash then the flag will be lost. // But assign to old server is not big deal because it not effect correctness. // See HBASE-23035 for more details. forceNewPlan = true; if (remoteProc != null) { // this means we are waiting for the sub procedure, so wake it up remoteProc.serverCrashed(env, regionNode, serverName); } else { // we are in RUNNING state, just update the region state, and we will process it later. env.getAssignmentManager().regionClosedAbnormally(regionNode); } } void attachRemoteProc(RegionRemoteProcedureBase proc) { this.remoteProc = proc; } void unattachRemoteProc(RegionRemoteProcedureBase proc) { assert this.remoteProc == proc; this.remoteProc = null; } // will be called after we finish loading the meta entry for this region. // used to change the state of the region node if we have a sub procedure, as we may not persist // the state to meta yet. See the code in RegionRemoteProcedureBase.execute for more details. void stateLoaded(AssignmentManager am, RegionStateNode regionNode) { if (remoteProc != null) { remoteProc.stateLoaded(am, regionNode); } } @Override protected void rollbackState(MasterProcedureEnv env, RegionStateTransitionState state) throws IOException, InterruptedException { // no rollback throw new UnsupportedOperationException(); } @Override protected RegionStateTransitionState getState(int stateId) { return RegionStateTransitionState.forNumber(stateId); } @Override protected int getStateId(RegionStateTransitionState state) { return state.getNumber(); } @Override protected RegionStateTransitionState getInitialState() { return initialState; } private static TransitionType convert(RegionTransitionType type) { switch (type) { case ASSIGN: return TransitionType.ASSIGN; case UNASSIGN: return TransitionType.UNASSIGN; case MOVE: return TransitionType.MOVE; case REOPEN: return TransitionType.REOPEN; default: throw new IllegalArgumentException("Unknown RegionTransitionType: " + type); } } private static RegionTransitionType convert(TransitionType type) { switch (type) { case ASSIGN: return RegionTransitionType.ASSIGN; case UNASSIGN: return RegionTransitionType.UNASSIGN; case MOVE: return RegionTransitionType.MOVE; case REOPEN: return RegionTransitionType.REOPEN; default: throw new IllegalArgumentException("Unknown TransitionType: " + type); } } @Override protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException { super.serializeStateData(serializer); RegionStateTransitionStateData.Builder builder = RegionStateTransitionStateData.newBuilder() .setType(convert(type)).setForceNewPlan(forceNewPlan); if (assignCandidate != null) { builder.setAssignCandidate(ProtobufUtil.toServerName(assignCandidate)); } serializer.serialize(builder.build()); } @Override protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException { super.deserializeStateData(serializer); RegionStateTransitionStateData data = serializer.deserialize(RegionStateTransitionStateData.class); type = convert(data.getType()); setInitialAndLastState(); forceNewPlan = data.getForceNewPlan(); if (data.hasAssignCandidate()) { assignCandidate = ProtobufUtil.toServerName(data.getAssignCandidate()); } } @Override protected ProcedureMetrics getProcedureMetrics(MasterProcedureEnv env) { MetricsAssignmentManager metrics = env.getAssignmentManager().getAssignmentManagerMetrics(); switch (type) { case ASSIGN: return metrics.getAssignProcMetrics(); case UNASSIGN: return metrics.getUnassignProcMetrics(); case MOVE: return metrics.getMoveProcMetrics(); case REOPEN: return metrics.getReopenProcMetrics(); default: throw new IllegalArgumentException("Unknown transition type: " + type); } } @Override public void toStringClassDetails(StringBuilder sb) { super.toStringClassDetails(sb); if (initialState == RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE) { sb.append(", ASSIGN"); } else if (lastState == RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_CLOSED) { sb.append(", UNASSIGN"); } else { sb.append(", REOPEN/MOVE"); } } private static TransitRegionStateProcedure setOwner(MasterProcedureEnv env, TransitRegionStateProcedure proc) { proc.setOwner(env.getRequestUser().getShortName()); return proc; } public enum TransitionType { ASSIGN, UNASSIGN, MOVE, REOPEN } // Be careful that, when you call these 4 methods below, you need to manually attach the returned // procedure with the RegionStateNode, otherwise the procedure will quit immediately without doing // anything. See the comment in executeFromState to find out why we need this assumption. public static TransitRegionStateProcedure assign(MasterProcedureEnv env, RegionInfo region, @Nullable ServerName targetServer) { return assign(env, region, false, targetServer); } public static TransitRegionStateProcedure assign(MasterProcedureEnv env, RegionInfo region, boolean forceNewPlan, @Nullable ServerName targetServer) { return setOwner(env, new TransitRegionStateProcedure(env, region, targetServer, forceNewPlan, TransitionType.ASSIGN)); } public static TransitRegionStateProcedure unassign(MasterProcedureEnv env, RegionInfo region) { return setOwner(env, new TransitRegionStateProcedure(env, region, null, false, TransitionType.UNASSIGN)); } public static TransitRegionStateProcedure reopen(MasterProcedureEnv env, RegionInfo region) { return setOwner(env, new TransitRegionStateProcedure(env, region, null, false, TransitionType.REOPEN)); } public static TransitRegionStateProcedure move(MasterProcedureEnv env, RegionInfo region, @Nullable ServerName targetServer) { return setOwner(env, new TransitRegionStateProcedure(env, region, targetServer, targetServer == null, TransitionType.MOVE)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy