You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by zg...@apache.org on 2019/02/21 07:39:44 UTC

[hbase] branch master updated: HBASE-21783 Support exceed user/table/ns throttle quota if region server has available quota

This is an automated email from the ASF dual-hosted git repository.

zghao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/master by this push:
     new 9a55cbb  HBASE-21783 Support exceed user/table/ns throttle quota if region server has available quota
9a55cbb is described below

commit 9a55cbb2c1dfe5a13a6ceb323ac7edd23532f4b5
Author: meiyi <my...@gamil.com>
AuthorDate: Thu Feb 21 09:28:49 2019 +0800

    HBASE-21783 Support exceed user/table/ns throttle quota if region server has available quota
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 .../java/org/apache/hadoop/hbase/client/Admin.java |   8 ++
 .../org/apache/hadoop/hbase/client/AsyncAdmin.java |   8 ++
 .../hadoop/hbase/client/AsyncHBaseAdmin.java       |   5 +
 .../hbase/client/ConnectionImplementation.java     |   8 ++
 .../org/apache/hadoop/hbase/client/HBaseAdmin.java |  14 +++
 .../hadoop/hbase/client/RawAsyncHBaseAdmin.java    |  16 +++
 .../hbase/client/ShortCircuitMasterConnection.java |   8 ++
 .../apache/hadoop/hbase/quotas/QuotaTableUtil.java |  15 +++
 .../src/main/protobuf/Master.proto                 |  12 +++
 .../hadoop/hbase/coprocessor/MasterObserver.java   |  20 ++++
 .../hadoop/hbase/master/MasterCoprocessorHost.java |  19 ++++
 .../hadoop/hbase/master/MasterRpcServices.java     |  13 +++
 .../hadoop/hbase/quotas/DefaultOperationQuota.java |  54 ++++++----
 .../hadoop/hbase/quotas/ExceedOperationQuota.java  | 102 +++++++++++++++++++
 .../hadoop/hbase/quotas/MasterQuotaManager.java    |  33 +++++-
 .../org/apache/hadoop/hbase/quotas/QuotaCache.java |  15 +++
 .../org/apache/hadoop/hbase/quotas/QuotaUtil.java  |  86 ++++++++++++++++
 .../hbase/quotas/RegionServerRpcQuotaManager.java  |  12 ++-
 .../hbase/security/access/AccessController.java    |   6 ++
 .../hbase/client/TestAsyncQuotaAdminApi.java       |   6 ++
 .../apache/hadoop/hbase/quotas/TestQuotaAdmin.java |  42 ++++++++
 .../hadoop/hbase/quotas/TestQuotaThrottle.java     | 113 ++++++++++++++++++---
 .../security/access/TestAccessController.java      |  14 +++
 hbase-shell/src/main/ruby/hbase/quotas.rb          |   4 +
 hbase-shell/src/main/ruby/shell.rb                 |   2 +
 .../commands/disable_exceed_throttle_quota.rb      |  40 ++++++++
 .../shell/commands/enable_exceed_throttle_quota.rb |  50 +++++++++
 hbase-shell/src/test/ruby/hbase/quotas_test.rb     |  10 ++
 .../hadoop/hbase/thrift2/client/ThriftAdmin.java   |   6 ++
 29 files changed, 703 insertions(+), 38 deletions(-)

diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
index deaea8c..054702a 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
@@ -2813,6 +2813,14 @@ public interface Admin extends Abortable, Closeable {
   boolean isRpcThrottleEnabled() throws IOException;
 
   /**
+   * Switch the exceed throttle quota. If enabled, user/table/namespace throttle quota
+   * can be exceeded if region server has availble quota.
+   * @param enable Set to <code>true</code> to enable, <code>false</code> to disable.
+   * @return Previous exceed throttle enabled value
+   */
+  boolean exceedThrottleQuotaSwitch(final boolean enable) throws IOException;
+
+  /**
    * Fetches the table sizes on the filesystem as tracked by the HBase Master.
    */
   Map<TableName, Long> getSpaceQuotaTableSizes() throws IOException;
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
index 9abfe23..b45a040 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
@@ -1304,6 +1304,14 @@ public interface AsyncAdmin {
   CompletableFuture<Boolean> isRpcThrottleEnabled();
 
   /**
+   * Switch the exceed throttle quota. If enabled, user/table/namespace throttle quota
+   * can be exceeded if region server has availble quota.
+   * @param enable Set to <code>true</code> to enable, <code>false</code> to disable.
+   * @return Previous exceed throttle enabled value
+   */
+  CompletableFuture<Boolean> exceedThrottleQuotaSwitch(boolean enable);
+
+  /**
    * Fetches the table sizes on the filesystem as tracked by the HBase Master.
    */
   CompletableFuture<Map<TableName, Long>> getSpaceQuotaTableSizes();
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
index f39fe36..960b72a 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
@@ -771,6 +771,11 @@ class AsyncHBaseAdmin implements AsyncAdmin {
   }
 
   @Override
+  public CompletableFuture<Boolean> exceedThrottleQuotaSwitch(boolean enable) {
+    return wrap(rawAdmin.exceedThrottleQuotaSwitch(enable));
+  }
+
+  @Override
   public CompletableFuture<Map<TableName, Long>> getSpaceQuotaTableSizes() {
     return wrap(rawAdmin.getSpaceQuotaTableSizes());
   }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionImplementation.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionImplementation.java
index 5df3c07..ff2ff2f 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionImplementation.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionImplementation.java
@@ -115,6 +115,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SecurityCa
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SecurityCapabilitiesResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetNormalizerRunningRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetNormalizerRunningResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchExceedThrottleQuotaRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchExceedThrottleQuotaResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchRpcThrottleRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchRpcThrottleResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetQuotaStatesRequest;
@@ -1777,6 +1779,12 @@ class ConnectionImplementation implements ClusterConnection, Closeable {
       }
 
       @Override
+      public SwitchExceedThrottleQuotaResponse switchExceedThrottleQuota(RpcController controller,
+          SwitchExceedThrottleQuotaRequest request) throws ServiceException {
+        return stub.switchExceedThrottleQuota(controller, request);
+      }
+
+      @Override
       public AccessControlProtos.GrantResponse grant(RpcController controller,
           AccessControlProtos.GrantRequest request) throws ServiceException {
         return stub.grant(controller, request);
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
index 769ddd7..f740218 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
@@ -4392,6 +4392,20 @@ public class HBaseAdmin implements Admin {
   }
 
   @Override
+  public boolean exceedThrottleQuotaSwitch(final boolean enable) throws IOException {
+    return executeCallable(new MasterCallable<Boolean>(getConnection(), getRpcControllerFactory()) {
+      @Override
+      protected Boolean rpcCall() throws Exception {
+        return this.master
+            .switchExceedThrottleQuota(getRpcController(),
+              MasterProtos.SwitchExceedThrottleQuotaRequest.newBuilder()
+                  .setExceedThrottleQuotaEnabled(enable).build())
+            .getPreviousExceedThrottleQuotaEnabled();
+      }
+    });
+  }
+
+  @Override
   public Map<TableName, Long> getSpaceQuotaTableSizes() throws IOException {
     return executeCallable(
       new MasterCallable<Map<TableName, Long>>(getConnection(), getRpcControllerFactory()) {
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
index 0e3b945..8dc3b01 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
@@ -257,6 +257,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SplitTable
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SplitTableRegionResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.StopMasterRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.StopMasterResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchExceedThrottleQuotaRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchExceedThrottleQuotaResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchRpcThrottleRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchRpcThrottleResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.TruncateTableRequest;
@@ -3684,6 +3686,20 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
   }
 
   @Override
+  public CompletableFuture<Boolean> exceedThrottleQuotaSwitch(boolean enable) {
+    CompletableFuture<Boolean> future = this.<Boolean> newMasterCaller()
+        .action((controller, stub) -> this
+            .<SwitchExceedThrottleQuotaRequest, SwitchExceedThrottleQuotaResponse, Boolean> call(
+              controller, stub,
+              SwitchExceedThrottleQuotaRequest.newBuilder().setExceedThrottleQuotaEnabled(enable)
+                  .build(),
+              (s, c, req, done) -> s.switchExceedThrottleQuota(c, req, done),
+              resp -> resp.getPreviousExceedThrottleQuotaEnabled()))
+        .call();
+    return future;
+  }
+
+  @Override
   public CompletableFuture<Map<TableName, Long>> getSpaceQuotaTableSizes() {
     return this.<Map<TableName, Long>> newMasterCaller().action((controller, stub) -> this
       .<GetSpaceQuotaRegionSizesRequest, GetSpaceQuotaRegionSizesResponse,
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ShortCircuitMasterConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ShortCircuitMasterConnection.java
index 02935548..090bcf9 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ShortCircuitMasterConnection.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ShortCircuitMasterConnection.java
@@ -153,6 +153,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SplitTable
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SplitTableRegionResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.StopMasterRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.StopMasterResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchExceedThrottleQuotaRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchExceedThrottleQuotaResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchRpcThrottleRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchRpcThrottleResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.TruncateTableRequest;
@@ -670,6 +672,12 @@ public class ShortCircuitMasterConnection implements MasterKeepAliveConnection {
   }
 
   @Override
+  public SwitchExceedThrottleQuotaResponse switchExceedThrottleQuota(RpcController controller,
+      SwitchExceedThrottleQuotaRequest request) throws ServiceException {
+    return stub.switchExceedThrottleQuota(controller, request);
+  }
+
+  @Override
   public GrantResponse grant(RpcController controller, GrantRequest request)
       throws ServiceException {
     return stub.grant(controller, request);
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaTableUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaTableUtil.java
index 42ab58f..b932242 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaTableUtil.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaTableUtil.java
@@ -100,6 +100,8 @@ public class QuotaTableUtil {
   protected static final byte[] QUOTA_TABLE_ROW_KEY_PREFIX = Bytes.toBytes("t.");
   protected static final byte[] QUOTA_NAMESPACE_ROW_KEY_PREFIX = Bytes.toBytes("n.");
   protected static final byte[] QUOTA_REGION_SERVER_ROW_KEY_PREFIX = Bytes.toBytes("r.");
+  private static final byte[] QUOTA_EXCEED_THROTTLE_QUOTA_ROW_KEY =
+      Bytes.toBytes("exceedThrottleQuota");
 
   /*
    * TODO: Setting specified region server quota isn't supported currently and the row key "r.all"
@@ -359,6 +361,11 @@ public class QuotaTableUtil {
       parseUserResult(result, visitor);
     } else if (isRegionServerRowKey(row)) {
       parseRegionServerResult(result, visitor);
+    } else if (isExceedThrottleQuotaRowKey(row)) {
+      // skip exceed throttle quota row key
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Skip exceedThrottleQuota row-key when parse quota result");
+      }
     } else {
       LOG.warn("unexpected row-key: " + Bytes.toString(row));
     }
@@ -698,6 +705,10 @@ public class QuotaTableUtil {
     return getRowKeyRegEx(QUOTA_REGION_SERVER_ROW_KEY_PREFIX, regionServer);
   }
 
+  protected static byte[] getExceedThrottleQuotaRowKey() {
+    return QUOTA_EXCEED_THROTTLE_QUOTA_ROW_KEY;
+  }
+
   private static String getRowKeyRegEx(final byte[] prefix, final String regex) {
     return '^' + Pattern.quote(Bytes.toString(prefix)) + regex + '$';
   }
@@ -724,6 +735,10 @@ public class QuotaTableUtil {
     return Bytes.startsWith(key, QUOTA_REGION_SERVER_ROW_KEY_PREFIX);
   }
 
+  private static boolean isExceedThrottleQuotaRowKey(final byte[] key) {
+    return Bytes.equals(key, QUOTA_EXCEED_THROTTLE_QUOTA_ROW_KEY);
+  }
+
   protected static String getRegionServerFromRowKey(final byte[] key) {
     return Bytes.toString(key, QUOTA_REGION_SERVER_ROW_KEY_PREFIX.length);
   }
diff --git a/hbase-protocol-shaded/src/main/protobuf/Master.proto b/hbase-protocol-shaded/src/main/protobuf/Master.proto
index ec0b543..4ed0ad5 100644
--- a/hbase-protocol-shaded/src/main/protobuf/Master.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/Master.proto
@@ -652,6 +652,14 @@ message IsRpcThrottleEnabledResponse {
   required bool rpc_throttle_enabled = 1;
 }
 
+message SwitchExceedThrottleQuotaRequest {
+  required bool exceed_throttle_quota_enabled = 1;
+}
+
+message SwitchExceedThrottleQuotaResponse {
+  required bool previous_exceed_throttle_quota_enabled = 1;
+}
+
 service MasterService {
   /** Used by the client to get the number of regions that have received the updated schema */
   rpc GetSchemaAlterStatus(GetSchemaAlterStatusRequest)
@@ -1016,6 +1024,10 @@ service MasterService {
   rpc IsRpcThrottleEnabled (IsRpcThrottleEnabledRequest)
     returns (IsRpcThrottleEnabledResponse);
 
+  /** Turn the exceed throttle quota on or off */
+  rpc SwitchExceedThrottleQuota (SwitchExceedThrottleQuotaRequest)
+    returns (SwitchExceedThrottleQuotaResponse);
+
   rpc Grant(GrantRequest) returns (GrantResponse);
 
   rpc Revoke(RevokeRequest) returns (RevokeResponse);
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java
index 68e69b9..d8b7d4d 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java
@@ -1576,6 +1576,26 @@ public interface MasterObserver {
   }
 
   /**
+   * Called before switching exceed throttle quota state.
+   * @param ctx the coprocessor instance's environment
+   * @param enable the exceed throttle quota value
+   */
+  default void preSwitchExceedThrottleQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx,
+      final boolean enable) throws IOException {
+  }
+
+  /**
+   * Called after switching exceed throttle quota state.
+   * @param ctx the coprocessor instance's environment
+   * @param oldValue the previously exceed throttle quota value
+   * @param newValue the newly exceed throttle quota value
+   */
+  default void postSwitchExceedThrottleQuota(
+      final ObserverContext<MasterCoprocessorEnvironment> ctx, final boolean oldValue,
+      final boolean newValue) throws IOException {
+  }
+
+  /**
    * Called before granting user permissions.
    * @param ctx the coprocessor instance's environment
    * @param userPermission the user and permissions
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java
index d999dae..8764143 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java
@@ -1836,6 +1836,25 @@ public class MasterCoprocessorHost
     });
   }
 
+  public void preSwitchExceedThrottleQuota(boolean enable) throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+      @Override
+      public void call(MasterObserver observer) throws IOException {
+        observer.preSwitchExceedThrottleQuota(this, enable);
+      }
+    });
+  }
+
+  public void postSwitchExceedThrottleQuota(final boolean oldValue, final boolean newValue)
+      throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+      @Override
+      public void call(MasterObserver observer) throws IOException {
+        observer.postSwitchExceedThrottleQuota(this, oldValue, newValue);
+      }
+    });
+  }
+
   public void preGrant(UserPermission userPermission, boolean mergeExistingPermissions)
       throws IOException {
     execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
index 1ff3f0e..e5fc0b8 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
@@ -266,6 +266,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SplitTable
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SplitTableRegionResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.StopMasterRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.StopMasterResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchExceedThrottleQuotaRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchExceedThrottleQuotaResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchRpcThrottleRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchRpcThrottleResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.TruncateTableRequest;
@@ -2511,6 +2513,17 @@ public class MasterRpcServices extends RSRpcServices
   }
 
   @Override
+  public SwitchExceedThrottleQuotaResponse switchExceedThrottleQuota(RpcController controller,
+      SwitchExceedThrottleQuotaRequest request) throws ServiceException {
+    try {
+      master.checkInitialized();
+      return master.getMasterQuotaManager().switchExceedThrottleQuota(request);
+    } catch (Exception e) {
+      throw new ServiceException(e);
+    }
+  }
+
+  @Override
   public GrantResponse grant(RpcController controller, GrantRequest request)
       throws ServiceException {
     try {
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/DefaultOperationQuota.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/DefaultOperationQuota.java
index f9b3ca5..6f0e5cd 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/DefaultOperationQuota.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/DefaultOperationQuota.java
@@ -34,17 +34,27 @@ import org.apache.hadoop.hbase.client.Result;
 public class DefaultOperationQuota implements OperationQuota {
   private static final Logger LOG = LoggerFactory.getLogger(DefaultOperationQuota.class);
 
-  private final List<QuotaLimiter> limiters;
+  protected final List<QuotaLimiter> limiters;
   private final long writeCapacityUnit;
   private final long readCapacityUnit;
 
-  private long writeAvailable = 0;
-  private long readAvailable = 0;
-  private long writeConsumed = 0;
-  private long readConsumed = 0;
-  private long writeCapacityUnitConsumed = 0;
-  private long readCapacityUnitConsumed = 0;
+  // the available read/write quota size in bytes
+  protected long writeAvailable = 0;
+  protected long readAvailable = 0;
+  // estimated quota
+  protected long writeConsumed = 0;
+  protected long readConsumed = 0;
+  protected long writeCapacityUnitConsumed = 0;
+  protected long readCapacityUnitConsumed = 0;
+  // real consumed quota
   private final long[] operationSize;
+  // difference between estimated quota and real consumed quota used in close method
+  // to adjust quota amount. Also used by ExceedOperationQuota which is a subclass
+  // of DefaultOperationQuota
+  protected long writeDiff = 0;
+  protected long readDiff = 0;
+  protected long writeCapacityUnitDiff = 0;
+  protected long readCapacityUnitDiff = 0;
 
   public DefaultOperationQuota(final Configuration conf, final QuotaLimiter... limiters) {
     this(conf, Arrays.asList(limiters));
@@ -69,12 +79,7 @@ public class DefaultOperationQuota implements OperationQuota {
 
   @Override
   public void checkQuota(int numWrites, int numReads, int numScans) throws RpcThrottlingException {
-    writeConsumed = estimateConsume(OperationType.MUTATE, numWrites, 100);
-    readConsumed = estimateConsume(OperationType.GET, numReads, 100);
-    readConsumed += estimateConsume(OperationType.SCAN, numScans, 1000);
-
-    writeCapacityUnitConsumed = calculateWriteCapacityUnit(writeConsumed);
-    readCapacityUnitConsumed = calculateReadCapacityUnit(readConsumed);
+    updateEstimateConsumeQuota(numWrites, numReads, numScans);
 
     writeAvailable = Long.MAX_VALUE;
     readAvailable = Long.MAX_VALUE;
@@ -96,12 +101,12 @@ public class DefaultOperationQuota implements OperationQuota {
   @Override
   public void close() {
     // Adjust the quota consumed for the specified operation
-    long writeDiff = operationSize[OperationType.MUTATE.ordinal()] - writeConsumed;
-    long readDiff = operationSize[OperationType.GET.ordinal()]
+    writeDiff = operationSize[OperationType.MUTATE.ordinal()] - writeConsumed;
+    readDiff = operationSize[OperationType.GET.ordinal()]
         + operationSize[OperationType.SCAN.ordinal()] - readConsumed;
-    long writeCapacityUnitDiff = calculateWriteCapacityUnitDiff(
+    writeCapacityUnitDiff = calculateWriteCapacityUnitDiff(
       operationSize[OperationType.MUTATE.ordinal()], writeConsumed);
-    long readCapacityUnitDiff = calculateReadCapacityUnitDiff(
+    readCapacityUnitDiff = calculateReadCapacityUnitDiff(
       operationSize[OperationType.GET.ordinal()] + operationSize[OperationType.SCAN.ordinal()],
       readConsumed);
 
@@ -140,6 +145,21 @@ public class DefaultOperationQuota implements OperationQuota {
     operationSize[OperationType.MUTATE.ordinal()] += QuotaUtil.calculateMutationSize(mutation);
   }
 
+  /**
+   * Update estimate quota(read/write size/capacityUnits) which will be consumed
+   * @param numWrites the number of write requests
+   * @param numReads the number of read requests
+   * @param numScans the number of scan requests
+   */
+  protected void updateEstimateConsumeQuota(int numWrites, int numReads, int numScans) {
+    writeConsumed = estimateConsume(OperationType.MUTATE, numWrites, 100);
+    readConsumed = estimateConsume(OperationType.GET, numReads, 100);
+    readConsumed += estimateConsume(OperationType.SCAN, numScans, 1000);
+
+    writeCapacityUnitConsumed = calculateWriteCapacityUnit(writeConsumed);
+    readCapacityUnitConsumed = calculateReadCapacityUnit(readConsumed);
+  }
+
   private long estimateConsume(final OperationType type, int numReqs, long avgSize) {
     if (numReqs > 0) {
       return avgSize * numReqs;
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/ExceedOperationQuota.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/ExceedOperationQuota.java
new file mode 100644
index 0000000..5c9541e
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/ExceedOperationQuota.java
@@ -0,0 +1,102 @@
+/**
+ * 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.quotas;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.yetus.audience.InterfaceStability;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/*
+ * Internal class used to check and consume quota if exceed throttle quota is enabled. Exceed
+ * throttle quota means, user can over consume user/namespace/table quota if region server has
+ * additional available quota because other users don't consume at the same time.
+ *
+ * There are some limits when enable exceed throttle quota:
+ * 1. Must set at least one read and one write region server throttle quota;
+ * 2. All region server throttle quotas must be in seconds time unit. Because once previous requests
+ * exceed their quota and consume region server quota, quota in other time units may be refilled in
+ * a long time, this may affect later requests.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+public class ExceedOperationQuota extends DefaultOperationQuota {
+  private static final Logger LOG = LoggerFactory.getLogger(ExceedOperationQuota.class);
+  private QuotaLimiter regionServerLimiter;
+
+  public ExceedOperationQuota(final Configuration conf, QuotaLimiter regionServerLimiter,
+      final QuotaLimiter... limiters) {
+    super(conf, limiters);
+    this.regionServerLimiter = regionServerLimiter;
+  }
+
+  @Override
+  public void checkQuota(int numWrites, int numReads, int numScans) throws RpcThrottlingException {
+    if (regionServerLimiter.isBypass()) {
+      // If region server limiter is bypass, which means no region server quota is set, check and
+      // throttle by all other quotas. In this condition, exceed throttle quota will not work.
+      LOG.warn("Exceed throttle quota is enabled but no region server quotas found");
+      super.checkQuota(numWrites, numReads, numScans);
+    } else {
+      // 1. Update estimate quota which will be consumed
+      updateEstimateConsumeQuota(numWrites, numReads, numScans);
+      // 2. Check if region server limiter is enough. If not, throw RpcThrottlingException.
+      regionServerLimiter.checkQuota(numWrites, writeConsumed, numReads + numScans, readConsumed,
+        writeCapacityUnitConsumed, readCapacityUnitConsumed);
+      // 3. Check if other limiters are enough. If not, exceed other limiters because region server
+      // limiter is enough.
+      boolean exceed = false;
+      try {
+        super.checkQuota(numWrites, numReads, numScans);
+      } catch (RpcThrottlingException e) {
+        exceed = true;
+        if (LOG.isDebugEnabled()) {
+          LOG.debug("Read/Write requests num exceeds quota: writes:{} reads:{} scan:{}, "
+              + "try use region server quota",
+            numWrites, numReads, numScans);
+        }
+      }
+      // 4. Region server limiter is enough and grab estimated consume quota.
+      readAvailable = Math.max(readAvailable, regionServerLimiter.getReadAvailable());
+      writeAvailable = Math.max(writeAvailable, regionServerLimiter.getWriteAvailable());
+      regionServerLimiter.grabQuota(numWrites, writeConsumed, numReads + numScans, readConsumed,
+        writeCapacityUnitConsumed, writeCapacityUnitConsumed);
+      if (exceed) {
+        // 5. Other quota limiter is exceeded and has not been grabbed (because throw
+        // RpcThrottlingException in Step 3), so grab it.
+        for (final QuotaLimiter limiter : limiters) {
+          limiter.grabQuota(numWrites, writeConsumed, numReads + numScans, readConsumed,
+            writeCapacityUnitConsumed, writeCapacityUnitConsumed);
+        }
+      }
+    }
+  }
+
+  @Override
+  public void close() {
+    super.close();
+    if (writeDiff != 0) {
+      regionServerLimiter.consumeWrite(writeDiff, writeCapacityUnitDiff);
+    }
+    if (readDiff != 0) {
+      regionServerLimiter.consumeRead(readDiff, readCapacityUnitDiff);
+    }
+  }
+}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/MasterQuotaManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/MasterQuotaManager.java
index 389e179..ffef30e 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/MasterQuotaManager.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/MasterQuotaManager.java
@@ -58,6 +58,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsRpcThrot
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsRpcThrottleEnabledResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchExceedThrottleQuotaRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchExceedThrottleQuotaResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchRpcThrottleRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchRpcThrottleResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.FileArchiveNotificationRequest;
@@ -301,7 +303,7 @@ public class MasterQuotaManager implements RegionStateListener {
       @Override
       public void update(GlobalQuotaSettingsImpl quotaPojo) throws IOException {
         QuotaUtil.addNamespaceQuota(masterServices.getConnection(), namespace,
-            ((GlobalQuotaSettingsImpl) quotaPojo).toQuotas());
+          quotaPojo.toQuotas());
       }
       @Override
       public void delete() throws IOException {
@@ -330,7 +332,7 @@ public class MasterQuotaManager implements RegionStateListener {
       @Override
       public void update(GlobalQuotaSettingsImpl quotaPojo) throws IOException {
         QuotaUtil.addRegionServerQuota(masterServices.getConnection(), regionServer,
-          ((GlobalQuotaSettingsImpl) quotaPojo).toQuotas());
+          quotaPojo.toQuotas());
       }
 
       @Override
@@ -405,6 +407,33 @@ public class MasterQuotaManager implements RegionStateListener {
     }
   }
 
+  public SwitchExceedThrottleQuotaResponse
+      switchExceedThrottleQuota(SwitchExceedThrottleQuotaRequest request) throws IOException {
+    boolean enabled = request.getExceedThrottleQuotaEnabled();
+    if (initialized) {
+      masterServices.getMasterCoprocessorHost().preSwitchExceedThrottleQuota(enabled);
+      boolean previousEnabled =
+          QuotaUtil.isExceedThrottleQuotaEnabled(masterServices.getConnection());
+      if (previousEnabled == enabled) {
+        LOG.warn("Skip switch exceed throttle quota to {} because it's the same with old value",
+          enabled);
+      } else {
+        QuotaUtil.switchExceedThrottleQuota(masterServices.getConnection(), enabled);
+        LOG.info("{} switch exceed throttle quota from {} to {}",
+          masterServices.getClientIdAuditPrefix(), previousEnabled, enabled);
+      }
+      SwitchExceedThrottleQuotaResponse response = SwitchExceedThrottleQuotaResponse.newBuilder()
+          .setPreviousExceedThrottleQuotaEnabled(previousEnabled).build();
+      masterServices.getMasterCoprocessorHost().postSwitchExceedThrottleQuota(previousEnabled,
+        enabled);
+      return response;
+    } else {
+      LOG.warn("Skip switch exceed throttle quota to {} because quota is disabled", enabled);
+      return SwitchExceedThrottleQuotaResponse.newBuilder()
+          .setPreviousExceedThrottleQuotaEnabled(false).build();
+    }
+  }
+
   private void setQuota(final SetQuotaRequest req, final SetQuotaOperations quotaOps)
       throws IOException, InterruptedException {
     if (req.hasRemoveAll() && req.getRemoveAll() == true) {
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java
index 4736362..336d8c1 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java
@@ -71,6 +71,7 @@ public class QuotaCache implements Stoppable {
   private final ConcurrentHashMap<String, UserQuotaState> userQuotaCache = new ConcurrentHashMap<>();
   private final ConcurrentHashMap<String, QuotaState> regionServerQuotaCache =
       new ConcurrentHashMap<>();
+  private volatile boolean exceedThrottleQuotaEnabled = false;
   private final RegionServerServices rsServices;
 
   private QuotaRefresherChore refreshChore;
@@ -158,6 +159,10 @@ public class QuotaCache implements Stoppable {
     return getQuotaState(this.regionServerQuotaCache, regionServer).getGlobalLimiter();
   }
 
+  protected boolean isExceedThrottleQuotaEnabled() {
+    return exceedThrottleQuotaEnabled;
+  }
+
   /**
    * Returns the QuotaState requested. If the quota info is not in cache an empty one will be
    * returned and the quota request will be enqueued for the next cache refresh.
@@ -227,6 +232,7 @@ public class QuotaCache implements Stoppable {
       fetchTableQuotaState();
       fetchUserQuotaState();
       fetchRegionServerQuotaState();
+      fetchExceedThrottleQuota();
       lastUpdate = EnvironmentEdgeManager.currentTime();
     }
 
@@ -292,6 +298,15 @@ public class QuotaCache implements Stoppable {
         });
     }
 
+    private void fetchExceedThrottleQuota() {
+      try {
+        QuotaCache.this.exceedThrottleQuotaEnabled =
+            QuotaUtil.isExceedThrottleQuotaEnabled(rsServices.getConnection());
+      } catch (IOException e) {
+        LOG.warn("Unable to read if exceed throttle quota enabled from quota table", e);
+      }
+    }
+
     private <K, V extends QuotaState> void fetch(final String type,
         final ConcurrentHashMap<K, V> quotasMap, final Fetcher<K, V> fetcher) {
       long now = EnvironmentEdgeManager.currentTime();
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaUtil.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaUtil.java
index e562336..2bff4e9 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaUtil.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaUtil.java
@@ -19,12 +19,14 @@
 package org.apache.hadoop.hbase.quotas;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.Cell;
+import org.apache.hadoop.hbase.DoNotRetryIOException;
 import org.apache.hadoop.hbase.HColumnDescriptor;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.HTableDescriptor;
@@ -39,12 +41,16 @@ import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.regionserver.BloomType;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
+import org.apache.hadoop.hbase.util.Pair;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.yetus.audience.InterfaceStability;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.TimeUnit;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Throttle;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.TimedQuota;
 
 /**
  * Helper class to interact with the quota table
@@ -153,6 +159,86 @@ public class QuotaUtil extends QuotaTableUtil {
     deleteQuotas(connection, getRegionServerRowKey(regionServer));
   }
 
+  protected static void switchExceedThrottleQuota(final Connection connection,
+      boolean exceedThrottleQuotaEnabled) throws IOException {
+    if (exceedThrottleQuotaEnabled) {
+      checkRSQuotaToEnableExceedThrottle(
+        getRegionServerQuota(connection, QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY));
+    }
+
+    Put put = new Put(getExceedThrottleQuotaRowKey());
+    put.addColumn(QUOTA_FAMILY_INFO, QUOTA_QUALIFIER_SETTINGS,
+      Bytes.toBytes(exceedThrottleQuotaEnabled));
+    doPut(connection, put);
+  }
+
+  private static void checkRSQuotaToEnableExceedThrottle(Quotas quotas) throws IOException {
+    if (quotas != null && quotas.hasThrottle()) {
+      Throttle throttle = quotas.getThrottle();
+      // If enable exceed throttle quota, make sure that there are at least one read(req/read +
+      // num/size/cu) and one write(req/write + num/size/cu) region server throttle quotas.
+      boolean hasReadQuota = false;
+      boolean hasWriteQuota = false;
+      if (throttle.hasReqNum() || throttle.hasReqSize() || throttle.hasReqCapacityUnit()) {
+        hasReadQuota = true;
+        hasWriteQuota = true;
+      }
+      if (!hasReadQuota
+          && (throttle.hasReadNum() || throttle.hasReadSize() || throttle.hasReadCapacityUnit())) {
+        hasReadQuota = true;
+      }
+      if (!hasReadQuota) {
+        throw new DoNotRetryIOException(
+            "Please set at least one read region server quota before enable exceed throttle quota");
+      }
+      if (!hasWriteQuota && (throttle.hasWriteNum() || throttle.hasWriteSize()
+          || throttle.hasWriteCapacityUnit())) {
+        hasWriteQuota = true;
+      }
+      if (!hasWriteQuota) {
+        throw new DoNotRetryIOException("Please set at least one write region server quota "
+            + "before enable exceed throttle quota");
+      }
+      // If enable exceed throttle quota, make sure that region server throttle quotas are in
+      // seconds time unit. Because once previous requests exceed their quota and consume region
+      // server quota, quota in other time units may be refilled in a long time, this may affect
+      // later requests.
+      List<Pair<Boolean, TimedQuota>> list =
+          Arrays.asList(Pair.newPair(throttle.hasReqNum(), throttle.getReqNum()),
+            Pair.newPair(throttle.hasReadNum(), throttle.getReadNum()),
+            Pair.newPair(throttle.hasWriteNum(), throttle.getWriteNum()),
+            Pair.newPair(throttle.hasReqSize(), throttle.getReqSize()),
+            Pair.newPair(throttle.hasReadSize(), throttle.getReadSize()),
+            Pair.newPair(throttle.hasWriteSize(), throttle.getWriteSize()),
+            Pair.newPair(throttle.hasReqCapacityUnit(), throttle.getReqCapacityUnit()),
+            Pair.newPair(throttle.hasReadCapacityUnit(), throttle.getReadCapacityUnit()),
+            Pair.newPair(throttle.hasWriteCapacityUnit(), throttle.getWriteCapacityUnit()));
+      for (Pair<Boolean, TimedQuota> pair : list) {
+        if (pair.getFirst()) {
+          if (pair.getSecond().getTimeUnit() != TimeUnit.SECONDS) {
+            throw new DoNotRetryIOException("All region server quota must be "
+                + "in seconds time unit if enable exceed throttle quota");
+          }
+        }
+      }
+    } else {
+      // If enable exceed throttle quota, make sure that region server quota is already set
+      throw new DoNotRetryIOException(
+          "Please set region server quota before enable exceed throttle quota");
+    }
+  }
+
+  protected static boolean isExceedThrottleQuotaEnabled(final Connection connection)
+      throws IOException {
+    Get get = new Get(getExceedThrottleQuotaRowKey());
+    get.addColumn(QUOTA_FAMILY_INFO, QUOTA_QUALIFIER_SETTINGS);
+    Result result = doGet(connection, get);
+    if (result.isEmpty()) {
+      return false;
+    }
+    return Bytes.toBoolean(result.getValue(QUOTA_FAMILY_INFO, QUOTA_QUALIFIER_SETTINGS));
+  }
+
   private static void addQuotas(final Connection connection, final byte[] rowKey,
       final Quotas data) throws IOException {
     addQuotas(connection, rowKey, QUOTA_QUALIFIER_SETTINGS, data);
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java
index 99ff516..0f96de5 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java
@@ -138,14 +138,20 @@ public class RegionServerRpcQuotaManager {
         QuotaLimiter rsLimiter = quotaCache
             .getRegionServerQuotaLimiter(QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY);
         useNoop &= tableLimiter.isBypass() && nsLimiter.isBypass() && rsLimiter.isBypass();
+        boolean exceedThrottleQuotaEnabled = quotaCache.isExceedThrottleQuotaEnabled();
         if (LOG.isTraceEnabled()) {
           LOG.trace("get quota for ugi=" + ugi + " table=" + table + " userLimiter=" + userLimiter
               + " tableLimiter=" + tableLimiter + " nsLimiter=" + nsLimiter + " rsLimiter="
-              + rsLimiter);
+              + rsLimiter + " exceedThrottleQuotaEnabled=" + exceedThrottleQuotaEnabled);
         }
         if (!useNoop) {
-          return new DefaultOperationQuota(this.rsServices.getConfiguration(), userLimiter,
-              tableLimiter, nsLimiter, rsLimiter);
+          if (exceedThrottleQuotaEnabled) {
+            return new ExceedOperationQuota(this.rsServices.getConfiguration(), rsLimiter,
+                userLimiter, tableLimiter, nsLimiter);
+          } else {
+            return new DefaultOperationQuota(this.rsServices.getConfiguration(), userLimiter,
+                tableLimiter, nsLimiter, rsLimiter);
+          }
         }
       }
     }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java
index d6a2463..dcf44b8 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java
@@ -2575,6 +2575,12 @@ public class AccessController implements MasterCoprocessor, RegionCoprocessor,
     requirePermission(ctx, "isRpcThrottleEnabled", Action.ADMIN);
   }
 
+  @Override
+  public void preSwitchExceedThrottleQuota(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      boolean enable) throws IOException {
+    requirePermission(ctx, "switchExceedThrottleQuota", Action.ADMIN);
+  }
+
   /**
    * Returns the active user to which authorization checks should be applied.
    * If we are in the context of an RPC call, the remote user is used,
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncQuotaAdminApi.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncQuotaAdminApi.java
index 707cc87..07d436c 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncQuotaAdminApi.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncQuotaAdminApi.java
@@ -187,6 +187,12 @@ public class TestAsyncQuotaAdminApi extends TestAsyncAdminBase {
     assertEquals(true, future2.get().booleanValue());
   }
 
+  @Test
+  public void testSwitchExceedThrottleQuota() throws Exception {
+    AsyncAdmin admin = ASYNC_CONN.getAdmin();
+    assertEquals(false, admin.exceedThrottleQuotaSwitch(false).get().booleanValue());
+  }
+
   private void assertNumResults(int expected, final QuotaFilter filter) throws Exception {
     assertEquals(expected, countResults(filter));
   }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaAdmin.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaAdmin.java
index 88cd127..178c6aa 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaAdmin.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaAdmin.java
@@ -597,6 +597,48 @@ public class TestQuotaAdmin {
     testSwitchRpcThrottle(admin, false, true);
   }
 
+  @Test
+  public void testSwitchExceedThrottleQuota() throws IOException {
+    String regionServer = QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY;
+    Admin admin = TEST_UTIL.getAdmin();
+
+    try {
+      admin.exceedThrottleQuotaSwitch(true);
+      fail("should not come here, because can't enable exceed throttle quota "
+          + "if there is no region server quota");
+    } catch (IOException e) {
+      LOG.warn("Expected exception", e);
+    }
+
+    admin.setQuota(QuotaSettingsFactory.throttleRegionServer(regionServer,
+      ThrottleType.WRITE_NUMBER, 100, TimeUnit.SECONDS));
+    try {
+      admin.exceedThrottleQuotaSwitch(true);
+      fail("should not come here, because can't enable exceed throttle quota "
+          + "if there is no read region server quota");
+    } catch (IOException e) {
+      LOG.warn("Expected exception", e);
+    }
+
+    admin.setQuota(QuotaSettingsFactory.throttleRegionServer(regionServer, ThrottleType.READ_NUMBER,
+      20, TimeUnit.MINUTES));
+    try {
+      admin.exceedThrottleQuotaSwitch(true);
+      fail("should not come here, because can't enable exceed throttle quota "
+          + "because not all region server quota are in seconds time unit");
+    } catch (IOException e) {
+      LOG.warn("Expected exception", e);
+    }
+    admin.setQuota(QuotaSettingsFactory.throttleRegionServer(regionServer, ThrottleType.READ_NUMBER,
+      20, TimeUnit.SECONDS));
+
+    assertFalse(admin.exceedThrottleQuotaSwitch(true));
+    assertTrue(admin.exceedThrottleQuotaSwitch(true));
+    assertTrue(admin.exceedThrottleQuotaSwitch(false));
+    assertFalse(admin.exceedThrottleQuotaSwitch(false));
+    admin.setQuota(QuotaSettingsFactory.unthrottleRegionServer(regionServer));
+  }
+
   private void testSwitchRpcThrottle(Admin admin, boolean oldRpcThrottle, boolean newRpcThrottle)
       throws IOException {
     boolean state = admin.switchRpcThrottle(newRpcThrottle);
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaThrottle.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaThrottle.java
index aee9707..ac3dc15 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaThrottle.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaThrottle.java
@@ -579,20 +579,81 @@ public class TestQuotaThrottle {
     // requests are throttled by table quota
     admin.setQuota(QuotaSettingsFactory.throttleRegionServer(
       QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, ThrottleType.WRITE_NUMBER, 7, TimeUnit.MINUTES));
-    triggerCacheRefresh(false, false, true, false, true, TABLE_NAMES[0]);
+    triggerTableCacheRefresh(false, TABLE_NAMES[0]);
+    triggerRegionServerCacheRefresh(false);
     assertEquals(5, doPuts(10, tables[0]));
 
     // requests are throttled by region server quota
     admin.setQuota(QuotaSettingsFactory.throttleRegionServer(
       QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, ThrottleType.WRITE_NUMBER, 4, TimeUnit.MINUTES));
-    triggerCacheRefresh(false, false, false, false, true, TABLE_NAMES[0]);
+    triggerRegionServerCacheRefresh(false);
     assertEquals(4, doPuts(10, tables[0]));
 
     // unthrottle
     admin.setQuota(
       QuotaSettingsFactory.unthrottleRegionServer(QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY));
     admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[0]));
-    triggerCacheRefresh(true, false, true, false, true, TABLE_NAMES[0]);
+    triggerTableCacheRefresh(true, TABLE_NAMES[0]);
+    triggerRegionServerCacheRefresh(true);
+  }
+
+  @Test
+  public void testExceedThrottleQuota() throws Exception {
+    final Admin admin = TEST_UTIL.getAdmin();
+    admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[0], ThrottleType.WRITE_NUMBER, 5,
+      TimeUnit.MINUTES));
+    triggerTableCacheRefresh(false, TABLE_NAMES[0]);
+    admin.setQuota(QuotaSettingsFactory.throttleRegionServer(
+      QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, ThrottleType.WRITE_NUMBER, 20, TimeUnit.SECONDS));
+    admin.setQuota(QuotaSettingsFactory.throttleRegionServer(
+      QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, ThrottleType.READ_NUMBER, 10, TimeUnit.SECONDS));
+    triggerRegionServerCacheRefresh(false);
+
+    // enable exceed throttle quota
+    admin.exceedThrottleQuotaSwitch(true);
+    // exceed table limit and allowed by region server limit
+    triggerExceedThrottleQuotaCacheRefresh(true);
+    waitMinuteQuota();
+    assertEquals(10, doPuts(10, tables[0]));
+    // exceed table limit and throttled by region server limit
+    waitMinuteQuota();
+    assertEquals(20, doPuts(25, tables[0]));
+
+    // set region server limiter is lower than table limiter
+    admin.setQuota(QuotaSettingsFactory.throttleRegionServer(
+      QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, ThrottleType.WRITE_NUMBER, 2, TimeUnit.SECONDS));
+    triggerRegionServerCacheRefresh(false);
+    // throttled by region server limiter
+    waitMinuteQuota();
+    assertEquals(2, doPuts(10, tables[0]));
+    admin.setQuota(QuotaSettingsFactory.throttleRegionServer(
+      QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, ThrottleType.WRITE_NUMBER, 20, TimeUnit.SECONDS));
+    triggerRegionServerCacheRefresh(false);
+
+    // disable exceed throttle quota
+    admin.exceedThrottleQuotaSwitch(false);
+    triggerExceedThrottleQuotaCacheRefresh(false);
+    waitMinuteQuota();
+    // throttled by table limit
+    assertEquals(5, doPuts(10, tables[0]));
+
+    // enable exceed throttle quota and unthrottle region server
+    admin.exceedThrottleQuotaSwitch(true);
+    triggerExceedThrottleQuotaCacheRefresh(true);
+    waitMinuteQuota();
+    admin.setQuota(
+      QuotaSettingsFactory.unthrottleRegionServer(QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY));
+    triggerRegionServerCacheRefresh(true);
+    waitMinuteQuota();
+    // throttled by table limit
+    assertEquals(5, doPuts(10, tables[0]));
+
+    // disable exceed throttle quota
+    admin.exceedThrottleQuotaSwitch(false);
+    triggerExceedThrottleQuotaCacheRefresh(false);
+    // unthrottle table
+    admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[0]));
+    triggerTableCacheRefresh(true, TABLE_NAMES[0]);
   }
 
   private int doPuts(int maxOps, final Table... tables) throws Exception {
@@ -647,29 +708,39 @@ public class TestQuotaThrottle {
   }
 
   private void triggerUserCacheRefresh(boolean bypass, TableName... tables) throws Exception {
-    triggerCacheRefresh(bypass, true, false, false, false, tables);
+    triggerCacheRefresh(bypass, true, false, false, false, false, tables);
   }
 
   private void triggerTableCacheRefresh(boolean bypass, TableName... tables) throws Exception {
-    triggerCacheRefresh(bypass, false, true, false, false, tables);
+    triggerCacheRefresh(bypass, false, true, false, false, false, tables);
   }
 
   private void triggerNamespaceCacheRefresh(boolean bypass, TableName... tables) throws Exception {
-    triggerCacheRefresh(bypass, false, false, true, false, tables);
+    triggerCacheRefresh(bypass, false, false, true, false, false, tables);
+  }
+
+  private void triggerRegionServerCacheRefresh(boolean bypass) throws Exception {
+    triggerCacheRefresh(bypass, false, false, false, true, false);
+  }
+
+  private void triggerExceedThrottleQuotaCacheRefresh(boolean exceedEnabled) throws Exception {
+    triggerCacheRefresh(exceedEnabled, false, false, false, false, true);
   }
 
   private void triggerCacheRefresh(boolean bypass, boolean userLimiter, boolean tableLimiter,
-      boolean nsLimiter, boolean rsLimiter, final TableName... tables) throws Exception {
+      boolean nsLimiter, boolean rsLimiter, boolean exceedThrottleQuota, final TableName... tables)
+      throws Exception {
     envEdge.incValue(2 * REFRESH_TIME);
-    for (RegionServerThread rst: TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads()) {
-      RegionServerRpcQuotaManager quotaManager = rst.getRegionServer().getRegionServerRpcQuotaManager();
+    for (RegionServerThread rst : TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads()) {
+      RegionServerRpcQuotaManager quotaManager =
+          rst.getRegionServer().getRegionServerRpcQuotaManager();
       QuotaCache quotaCache = quotaManager.getQuotaCache();
 
       quotaCache.triggerCacheRefresh();
       // sleep for cache update
       Thread.sleep(250);
 
-      for (TableName table: tables) {
+      for (TableName table : tables) {
         quotaCache.getTableLimiter(table);
       }
 
@@ -677,7 +748,7 @@ public class TestQuotaThrottle {
       while (!isUpdated) {
         quotaCache.triggerCacheRefresh();
         isUpdated = true;
-        for (TableName table: tables) {
+        for (TableName table : tables) {
           boolean isBypass = true;
           if (userLimiter) {
             isBypass &= quotaCache.getUserLimiter(User.getCurrent().getUGI(), table).isBypass();
@@ -688,17 +759,27 @@ public class TestQuotaThrottle {
           if (nsLimiter) {
             isBypass &= quotaCache.getNamespaceLimiter(table.getNamespaceAsString()).isBypass();
           }
-          if (rsLimiter) {
-            isBypass &= quotaCache
-                .getRegionServerQuotaLimiter(QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY)
-                .isBypass();
-          }
           if (isBypass != bypass) {
             envEdge.incValue(100);
             isUpdated = false;
             break;
           }
         }
+        if (rsLimiter) {
+          boolean rsIsBypass = quotaCache
+              .getRegionServerQuotaLimiter(QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY).isBypass();
+          if (rsIsBypass != bypass) {
+            envEdge.incValue(100);
+            isUpdated = false;
+            continue;
+          }
+        }
+        if (exceedThrottleQuota) {
+          if (quotaCache.isExceedThrottleQuotaEnabled() != bypass) {
+            envEdge.incValue(100);
+            isUpdated = false;
+          }
+        }
       }
 
       LOG.debug("QuotaCache");
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java
index 5199550..2463eb0 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java
@@ -3517,6 +3517,20 @@ public class TestAccessController extends SecureTestUtil {
     verifyDenied(action, USER_CREATE, USER_RW, USER_RO, USER_NONE, USER_OWNER);
   }
 
+  @Test
+  public void testSwitchExceedThrottleQuota() throws Exception {
+    AccessTestAction action = new AccessTestAction() {
+      @Override
+      public Object run() throws Exception {
+        ACCESS_CONTROLLER.preSwitchExceedThrottleQuota(ObserverContextImpl.createAndPrepare(CP_ENV),
+          true);
+        return null;
+      }
+    };
+    verifyAllowed(action, SUPERUSER, USER_ADMIN);
+    verifyDenied(action, USER_CREATE, USER_RW, USER_RO, USER_NONE, USER_OWNER);
+  }
+
   /*
    * Validate Global User ACL
    */
diff --git a/hbase-shell/src/main/ruby/hbase/quotas.rb b/hbase-shell/src/main/ruby/hbase/quotas.rb
index 4023aed..a0f2263 100644
--- a/hbase-shell/src/main/ruby/hbase/quotas.rb
+++ b/hbase-shell/src/main/ruby/hbase/quotas.rb
@@ -264,6 +264,10 @@ module Hbase
       @admin.switchRpcThrottle(java.lang.Boolean.valueOf(enabled))
     end
 
+    def switch_exceed_throttle_quota(enabled)
+      @admin.exceedThrottleQuotaSwitch(java.lang.Boolean.valueOf(enabled))
+    end
+
     def _parse_size(str_limit)
       str_limit = str_limit.downcase
       match = /^(\d+)([bkmgtp%]?)$/.match(str_limit)
diff --git a/hbase-shell/src/main/ruby/shell.rb b/hbase-shell/src/main/ruby/shell.rb
index c22652d..a8e58ed 100644
--- a/hbase-shell/src/main/ruby/shell.rb
+++ b/hbase-shell/src/main/ruby/shell.rb
@@ -442,6 +442,8 @@ Shell.load_command_group(
     list_snapshot_sizes
     enable_rpc_throttle
     disable_rpc_throttle
+    enable_exceed_throttle_quota
+    disable_exceed_throttle_quota
   ]
 )
 
diff --git a/hbase-shell/src/main/ruby/shell/commands/disable_exceed_throttle_quota.rb b/hbase-shell/src/main/ruby/shell/commands/disable_exceed_throttle_quota.rb
new file mode 100644
index 0000000..2baa05a
--- /dev/null
+++ b/hbase-shell/src/main/ruby/shell/commands/disable_exceed_throttle_quota.rb
@@ -0,0 +1,40 @@
+#
+#
+# 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.
+#
+
+module Shell
+  module Commands
+    class DisableExceedThrottleQuota < Command
+      def help
+        <<-EOF
+Disable exceed throttle quota. Returns previous exceed throttle quota enabled value.
+NOTE: if quota is not enabled, this will not work and always return false.
+
+Examples:
+    hbase> disable_exceed_throttle_quota
+        EOF
+      end
+
+      def command
+        prev_state = quotas_admin.switch_exceed_throttle_quota(false) ? 'true' : 'false'
+        formatter.row(["Previous exceed throttle quota enabled : #{prev_state}"])
+        prev_state
+      end
+    end
+  end
+end
diff --git a/hbase-shell/src/main/ruby/shell/commands/enable_exceed_throttle_quota.rb b/hbase-shell/src/main/ruby/shell/commands/enable_exceed_throttle_quota.rb
new file mode 100644
index 0000000..5ab27c8
--- /dev/null
+++ b/hbase-shell/src/main/ruby/shell/commands/enable_exceed_throttle_quota.rb
@@ -0,0 +1,50 @@
+#
+#
+# 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.
+#
+
+module Shell
+  module Commands
+    class EnableExceedThrottleQuota < Command
+      def help
+        <<-EOF
+Enable exceed throttle quota. Returns previous exceed throttle quota enabled value.
+NOTE: if quota is not enabled, this will not work and always return false.
+
+If enabled, allow requests exceed user/table/namespace throttle quotas when region
+server has available quota.
+
+There are two limits if enable exceed throttle quota. First, please set region server
+quota. Second, please make sure that all region server throttle quotas are in seconds
+time unit, because once previous requests exceed their quota and consume region server
+quota, quota in other time units may be refilled in a long time, which may affect later
+requests.
+
+
+Examples:
+    hbase> enable_exceed_throttle_quota
+        EOF
+      end
+
+      def command
+        prev_state = quotas_admin.switch_exceed_throttle_quota(true) ? 'true' : 'false'
+        formatter.row(["Previous exceed throttle quota enabled : #{prev_state}"])
+        prev_state
+      end
+    end
+  end
+end
diff --git a/hbase-shell/src/test/ruby/hbase/quotas_test.rb b/hbase-shell/src/test/ruby/hbase/quotas_test.rb
index 1bcc07b..2d85054 100644
--- a/hbase-shell/src/test/ruby/hbase/quotas_test.rb
+++ b/hbase-shell/src/test/ruby/hbase/quotas_test.rb
@@ -272,6 +272,16 @@ module Hbase
       output = capture_stdout{ command(:list_quotas) }
       assert(output.include?('0 row(s)'))
     end
+
+    define_test 'switch exceed throttle quota' do
+      command(:set_quota, TYPE => THROTTLE, REGIONSERVER => 'all', LIMIT => '1CU/sec')
+      output = capture_stdout { command(:enable_exceed_throttle_quota) }
+      assert(output.include?('Previous exceed throttle quota enabled : false'))
+
+      output = capture_stdout { command(:disable_exceed_throttle_quota) }
+      assert(output.include?('Previous exceed throttle quota enabled : true'))
+      command(:set_quota, TYPE => THROTTLE, REGIONSERVER => 'all', LIMIT => NONE)
+    end
   end
   # rubocop:enable Metrics/ClassLength
 end
diff --git a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
index 5d550c6..4063a3c 100644
--- a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
+++ b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
@@ -513,6 +513,12 @@ public class ThriftAdmin implements Admin {
   }
 
   @Override
+  public boolean exceedThrottleQuotaSwitch(boolean enable) throws IOException {
+    throw new NotImplementedException(
+        "exceedThrottleQuotaSwitch by pattern not supported in ThriftAdmin");
+  }
+
+  @Override
   public HTableDescriptor[] disableTables(String regex) throws IOException {
     throw new NotImplementedException("disableTables by pattern not supported in ThriftAdmin");
   }