View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional information regarding
4    * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
7    * law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
8    * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
9    * for the specific language governing permissions and limitations under the License.
10   */
11  
12  package org.apache.hadoop.hbase.quotas;
13  
14  import java.io.IOException;
15  import java.util.List;
16  
17  import org.apache.commons.logging.Log;
18  import org.apache.commons.logging.LogFactory;
19  import org.apache.hadoop.hbase.HBaseIOException;
20  import org.apache.hadoop.hbase.TableName;
21  import org.apache.hadoop.hbase.classification.InterfaceAudience;
22  import org.apache.hadoop.hbase.classification.InterfaceStability;
23  import org.apache.hadoop.hbase.ipc.RpcScheduler;
24  import org.apache.hadoop.hbase.ipc.RpcServer;
25  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos;
26  import org.apache.hadoop.hbase.regionserver.Region;
27  import org.apache.hadoop.hbase.regionserver.RegionServerServices;
28  import org.apache.hadoop.hbase.security.User;
29  import org.apache.hadoop.security.UserGroupInformation;
30  
31  /**
32   * Region Server Quota Manager. It is responsible to provide access to the quota information of each
33   * user/table. The direct user of this class is the RegionServer that will get and check the
34   * user/table quota for each operation (put, get, scan). For system tables and user/table with a
35   * quota specified, the quota check will be a noop.
36   */
37  @InterfaceAudience.Private
38  @InterfaceStability.Evolving
39  public class RegionServerQuotaManager {
40    private static final Log LOG = LogFactory.getLog(RegionServerQuotaManager.class);
41  
42    private final RegionServerServices rsServices;
43  
44    private QuotaCache quotaCache = null;
45    private boolean useRetryableThrottlingException;
46  
47    public RegionServerQuotaManager(final RegionServerServices rsServices) {
48      this.rsServices = rsServices;
49    }
50  
51    public void start(final RpcScheduler rpcScheduler) throws IOException {
52      if (!QuotaUtil.isQuotaEnabled(rsServices.getConfiguration())) {
53        LOG.info("Quota support disabled");
54        return;
55      }
56  
57      LOG.info("Initializing quota support");
58  
59      // Initialize quota cache
60      quotaCache = new QuotaCache(rsServices);
61      quotaCache.start();
62  
63      useRetryableThrottlingException = rsServices.getConfiguration()
64          .getBoolean(QuotaUtil.QUOTA_RETRYABLE_THROTTING_EXCEPTION_CONF_KEY,
65              QuotaUtil.QUOTA_RETRYABLE_THROTTING_EXCEPTION_DEFAULT);
66    }
67  
68    public void stop() {
69      if (isQuotaEnabled()) {
70        quotaCache.stop("shutdown");
71      }
72    }
73  
74    public boolean isQuotaEnabled() {
75      return quotaCache != null;
76    }
77  
78    QuotaCache getQuotaCache() {
79      return quotaCache;
80    }
81  
82    /**
83     * Returns the quota for an operation.
84     * @param ugi the user that is executing the operation
85     * @param table the table where the operation will be executed
86     * @return the OperationQuota
87     */
88    public OperationQuota getQuota(final UserGroupInformation ugi, final TableName table) {
89      if (isQuotaEnabled() && !table.isSystemTable()) {
90        UserQuotaState userQuotaState = quotaCache.getUserQuotaState(ugi);
91        QuotaLimiter userLimiter = userQuotaState.getTableLimiter(table);
92        boolean useNoop = userLimiter.isBypass();
93        if (userQuotaState.hasBypassGlobals()) {
94          if (LOG.isTraceEnabled()) {
95            LOG.trace("get quota for ugi=" + ugi + " table=" + table + " userLimiter=" + userLimiter);
96          }
97          if (!useNoop) {
98            return new DefaultOperationQuota(this.rsServices.getConfiguration(), userLimiter);
99          }
100       } else {
101         QuotaLimiter nsLimiter = quotaCache.getNamespaceLimiter(table.getNamespaceAsString());
102         QuotaLimiter tableLimiter = quotaCache.getTableLimiter(table);
103         useNoop &= tableLimiter.isBypass() && nsLimiter.isBypass();
104         if (LOG.isTraceEnabled()) {
105           LOG.trace("get quota for ugi=" + ugi + " table=" + table + " userLimiter=" + userLimiter
106               + " tableLimiter=" + tableLimiter + " nsLimiter=" + nsLimiter);
107         }
108         if (!useNoop) {
109           return new DefaultOperationQuota(this.rsServices.getConfiguration(), userLimiter,
110             tableLimiter, nsLimiter);
111         }
112       }
113     }
114     return NoopOperationQuota.get();
115   }
116 
117   /**
118    * Check the quota for the current (rpc-context) user. Returns the OperationQuota used to get the
119    * available quota and to report the data/usage of the operation.
120    * @param region the region where the operation will be performed
121    * @param type the operation type
122    * @return the OperationQuota
123    * @throws ThrottlingException if the operation cannot be executed due to quota exceeded.
124    */
125   public OperationQuota checkQuota(final Region region, final OperationQuota.OperationType type)
126       throws IOException {
127     switch (type) {
128     case SCAN:
129       return checkQuota(region, 0, 0, 1);
130     case GET:
131       return checkQuota(region, 0, 1, 0);
132     case MUTATE:
133       return checkQuota(region, 1, 0, 0);
134     default:
135       throw new RuntimeException("Invalid operation type: " + type);
136     }
137   }
138 
139   /**
140    * Check the quota for the current (rpc-context) user. Returns the OperationQuota used to get the
141    * available quota and to report the data/usage of the operation.
142    * @param region the region where the operation will be performed
143    * @param actions the "multi" actions to perform
144    * @return the OperationQuota
145    * @throws ThrottlingException if the operation cannot be executed due to quota exceeded.
146    */
147   public OperationQuota checkQuota(final Region region, final List<ClientProtos.Action> actions)
148       throws IOException {
149     int numWrites = 0;
150     int numReads = 0;
151     for (final ClientProtos.Action action : actions) {
152       if (action.hasMutation()) {
153         numWrites++;
154       } else if (action.hasGet()) {
155         numReads++;
156       }
157     }
158     return checkQuota(region, numWrites, numReads, 0);
159   }
160 
161   /**
162    * Check the quota for the current (rpc-context) user. Returns the OperationQuota used to get the
163    * available quota and to report the data/usage of the operation.
164    * @param region the region where the operation will be performed
165    * @param numWrites number of writes to perform
166    * @param numReads number of short-reads to perform
167    * @param numScans number of scan to perform
168    * @return the OperationQuota
169    * @throws ThrottlingException if the operation cannot be executed due to quota exceeded.
170    */
171   private OperationQuota checkQuota(final Region region, final int numWrites, final int numReads,
172       final int numScans) throws IOException {
173     User user = RpcServer.getRequestUser();
174     UserGroupInformation ugi;
175     if (user != null) {
176       ugi = user.getUGI();
177     } else {
178       ugi = User.getCurrent().getUGI();
179     }
180     TableName table = region.getTableDesc().getTableName();
181 
182     OperationQuota quota = getQuota(ugi, table);
183     try {
184       quota.checkQuota(numWrites, numReads, numScans);
185     } catch (HBaseIOException e) {
186       LOG.debug("Throttling exception for user=" + ugi.getUserName() + " table=" + table
187           + " numWrites=" + numWrites + " numReads=" + numReads + " numScans=" + numScans + ": "
188           + e.getMessage());
189       // Depending on whether we are supposed to throw a retryable IO exeption or not, choose
190       // the correct exception type to (re)throw
191       if (useRetryableThrottlingException) {
192         throw e;
193       } else {
194         throw new ThrottlingException(e.getMessage());
195       }
196     }
197     return quota;
198   }
199 }