PageRenderTime 78ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java

http://github.com/apache/hbase
Java | 406 lines | 275 code | 52 blank | 79 comment | 24 complexity | d3f7447de5ec68535e4ed39d84ff7366 MD5 | raw file
Possible License(s): Apache-2.0, MIT
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.apache.hadoop.hbase.quotas;
  19. import static org.apache.hadoop.hbase.util.ConcurrentMapUtils.computeIfAbsent;
  20. import org.apache.hadoop.hbase.ClusterMetrics.Option;
  21. import org.apache.hadoop.hbase.MetaTableAccessor;
  22. import org.apache.hadoop.hbase.regionserver.HRegionServer;
  23. import java.io.IOException;
  24. import java.util.ArrayList;
  25. import java.util.EnumSet;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.Set;
  29. import java.util.concurrent.ConcurrentHashMap;
  30. import java.util.concurrent.ConcurrentMap;
  31. import org.apache.hadoop.conf.Configuration;
  32. import org.apache.hadoop.hbase.ScheduledChore;
  33. import org.apache.hadoop.hbase.Stoppable;
  34. import org.apache.hadoop.hbase.TableName;
  35. import org.apache.hadoop.hbase.client.Get;
  36. import org.apache.hadoop.hbase.regionserver.RegionServerServices;
  37. import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
  38. import org.apache.hadoop.security.UserGroupInformation;
  39. import org.apache.yetus.audience.InterfaceAudience;
  40. import org.apache.yetus.audience.InterfaceStability;
  41. import org.slf4j.Logger;
  42. import org.slf4j.LoggerFactory;
  43. /**
  44. * Cache that keeps track of the quota settings for the users and tables that
  45. * are interacting with it.
  46. *
  47. * To avoid blocking the operations if the requested quota is not in cache
  48. * an "empty quota" will be returned and the request to fetch the quota information
  49. * will be enqueued for the next refresh.
  50. *
  51. * TODO: At the moment the Cache has a Chore that will be triggered every 5min
  52. * or on cache-miss events. Later the Quotas will be pushed using the notification system.
  53. */
  54. @InterfaceAudience.Private
  55. @InterfaceStability.Evolving
  56. public class QuotaCache implements Stoppable {
  57. private static final Logger LOG = LoggerFactory.getLogger(QuotaCache.class);
  58. public static final String REFRESH_CONF_KEY = "hbase.quota.refresh.period";
  59. private static final int REFRESH_DEFAULT_PERIOD = 5 * 60000; // 5min
  60. private static final int EVICT_PERIOD_FACTOR = 5; // N * REFRESH_DEFAULT_PERIOD
  61. // for testing purpose only, enforce the cache to be always refreshed
  62. static boolean TEST_FORCE_REFRESH = false;
  63. private final ConcurrentMap<String, QuotaState> namespaceQuotaCache = new ConcurrentHashMap<>();
  64. private final ConcurrentMap<TableName, QuotaState> tableQuotaCache = new ConcurrentHashMap<>();
  65. private final ConcurrentMap<String, UserQuotaState> userQuotaCache = new ConcurrentHashMap<>();
  66. private final ConcurrentMap<String, QuotaState> regionServerQuotaCache =
  67. new ConcurrentHashMap<>();
  68. private volatile boolean exceedThrottleQuotaEnabled = false;
  69. // factors used to divide cluster scope quota into machine scope quota
  70. private volatile double machineQuotaFactor = 1;
  71. private final ConcurrentHashMap<TableName, Double> tableMachineQuotaFactors =
  72. new ConcurrentHashMap<>();
  73. private final RegionServerServices rsServices;
  74. private QuotaRefresherChore refreshChore;
  75. private boolean stopped = true;
  76. public QuotaCache(final RegionServerServices rsServices) {
  77. this.rsServices = rsServices;
  78. }
  79. public void start() throws IOException {
  80. stopped = false;
  81. // TODO: This will be replaced once we have the notification bus ready.
  82. Configuration conf = rsServices.getConfiguration();
  83. int period = conf.getInt(REFRESH_CONF_KEY, REFRESH_DEFAULT_PERIOD);
  84. refreshChore = new QuotaRefresherChore(period, this);
  85. rsServices.getChoreService().scheduleChore(refreshChore);
  86. }
  87. @Override
  88. public void stop(final String why) {
  89. if (refreshChore != null) {
  90. LOG.debug("Stopping QuotaRefresherChore chore.");
  91. refreshChore.shutdown(true);
  92. }
  93. stopped = true;
  94. }
  95. @Override
  96. public boolean isStopped() {
  97. return stopped;
  98. }
  99. /**
  100. * Returns the limiter associated to the specified user/table.
  101. *
  102. * @param ugi the user to limit
  103. * @param table the table to limit
  104. * @return the limiter associated to the specified user/table
  105. */
  106. public QuotaLimiter getUserLimiter(final UserGroupInformation ugi, final TableName table) {
  107. if (table.isSystemTable()) {
  108. return NoopQuotaLimiter.get();
  109. }
  110. return getUserQuotaState(ugi).getTableLimiter(table);
  111. }
  112. /**
  113. * Returns the QuotaState associated to the specified user.
  114. * @param ugi the user
  115. * @return the quota info associated to specified user
  116. */
  117. public UserQuotaState getUserQuotaState(final UserGroupInformation ugi) {
  118. return computeIfAbsent(userQuotaCache, ugi.getShortUserName(), UserQuotaState::new,
  119. this::triggerCacheRefresh);
  120. }
  121. /**
  122. * Returns the limiter associated to the specified table.
  123. *
  124. * @param table the table to limit
  125. * @return the limiter associated to the specified table
  126. */
  127. public QuotaLimiter getTableLimiter(final TableName table) {
  128. return getQuotaState(this.tableQuotaCache, table).getGlobalLimiter();
  129. }
  130. /**
  131. * Returns the limiter associated to the specified namespace.
  132. *
  133. * @param namespace the namespace to limit
  134. * @return the limiter associated to the specified namespace
  135. */
  136. public QuotaLimiter getNamespaceLimiter(final String namespace) {
  137. return getQuotaState(this.namespaceQuotaCache, namespace).getGlobalLimiter();
  138. }
  139. /**
  140. * Returns the limiter associated to the specified region server.
  141. *
  142. * @param regionServer the region server to limit
  143. * @return the limiter associated to the specified region server
  144. */
  145. public QuotaLimiter getRegionServerQuotaLimiter(final String regionServer) {
  146. return getQuotaState(this.regionServerQuotaCache, regionServer).getGlobalLimiter();
  147. }
  148. protected boolean isExceedThrottleQuotaEnabled() {
  149. return exceedThrottleQuotaEnabled;
  150. }
  151. /**
  152. * Returns the QuotaState requested. If the quota info is not in cache an empty one will be
  153. * returned and the quota request will be enqueued for the next cache refresh.
  154. */
  155. private <K> QuotaState getQuotaState(final ConcurrentMap<K, QuotaState> quotasMap,
  156. final K key) {
  157. return computeIfAbsent(quotasMap, key, QuotaState::new, this::triggerCacheRefresh);
  158. }
  159. void triggerCacheRefresh() {
  160. refreshChore.triggerNow();
  161. }
  162. long getLastUpdate() {
  163. return refreshChore.lastUpdate;
  164. }
  165. Map<String, QuotaState> getNamespaceQuotaCache() {
  166. return namespaceQuotaCache;
  167. }
  168. Map<String, QuotaState> getRegionServerQuotaCache() {
  169. return regionServerQuotaCache;
  170. }
  171. Map<TableName, QuotaState> getTableQuotaCache() {
  172. return tableQuotaCache;
  173. }
  174. Map<String, UserQuotaState> getUserQuotaCache() {
  175. return userQuotaCache;
  176. }
  177. // TODO: Remove this once we have the notification bus
  178. private class QuotaRefresherChore extends ScheduledChore {
  179. private long lastUpdate = 0;
  180. public QuotaRefresherChore(final int period, final Stoppable stoppable) {
  181. super("QuotaRefresherChore", stoppable, period);
  182. }
  183. @Override
  184. @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="GC_UNRELATED_TYPES",
  185. justification="I do not understand why the complaints, it looks good to me -- FIX")
  186. protected void chore() {
  187. // Prefetch online tables/namespaces
  188. for (TableName table: ((HRegionServer)QuotaCache.this.rsServices).getOnlineTables()) {
  189. if (table.isSystemTable()) {
  190. continue;
  191. }
  192. QuotaCache.this.tableQuotaCache.computeIfAbsent(table, key -> new QuotaState());
  193. final String ns = table.getNamespaceAsString();
  194. QuotaCache.this.namespaceQuotaCache.computeIfAbsent(ns, key -> new QuotaState());
  195. }
  196. QuotaCache.this.regionServerQuotaCache.computeIfAbsent(
  197. QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, key -> new QuotaState());
  198. updateQuotaFactors();
  199. fetchNamespaceQuotaState();
  200. fetchTableQuotaState();
  201. fetchUserQuotaState();
  202. fetchRegionServerQuotaState();
  203. fetchExceedThrottleQuota();
  204. lastUpdate = EnvironmentEdgeManager.currentTime();
  205. }
  206. private void fetchNamespaceQuotaState() {
  207. fetch("namespace", QuotaCache.this.namespaceQuotaCache, new Fetcher<String, QuotaState>() {
  208. @Override
  209. public Get makeGet(final Map.Entry<String, QuotaState> entry) {
  210. return QuotaUtil.makeGetForNamespaceQuotas(entry.getKey());
  211. }
  212. @Override
  213. public Map<String, QuotaState> fetchEntries(final List<Get> gets)
  214. throws IOException {
  215. return QuotaUtil.fetchNamespaceQuotas(rsServices.getConnection(), gets,
  216. machineQuotaFactor);
  217. }
  218. });
  219. }
  220. private void fetchTableQuotaState() {
  221. fetch("table", QuotaCache.this.tableQuotaCache, new Fetcher<TableName, QuotaState>() {
  222. @Override
  223. public Get makeGet(final Map.Entry<TableName, QuotaState> entry) {
  224. return QuotaUtil.makeGetForTableQuotas(entry.getKey());
  225. }
  226. @Override
  227. public Map<TableName, QuotaState> fetchEntries(final List<Get> gets)
  228. throws IOException {
  229. return QuotaUtil.fetchTableQuotas(rsServices.getConnection(), gets,
  230. tableMachineQuotaFactors);
  231. }
  232. });
  233. }
  234. private void fetchUserQuotaState() {
  235. final Set<String> namespaces = QuotaCache.this.namespaceQuotaCache.keySet();
  236. final Set<TableName> tables = QuotaCache.this.tableQuotaCache.keySet();
  237. fetch("user", QuotaCache.this.userQuotaCache, new Fetcher<String, UserQuotaState>() {
  238. @Override
  239. public Get makeGet(final Map.Entry<String, UserQuotaState> entry) {
  240. return QuotaUtil.makeGetForUserQuotas(entry.getKey(), tables, namespaces);
  241. }
  242. @Override
  243. public Map<String, UserQuotaState> fetchEntries(final List<Get> gets)
  244. throws IOException {
  245. return QuotaUtil.fetchUserQuotas(rsServices.getConnection(), gets,
  246. tableMachineQuotaFactors, machineQuotaFactor);
  247. }
  248. });
  249. }
  250. private void fetchRegionServerQuotaState() {
  251. fetch("regionServer", QuotaCache.this.regionServerQuotaCache,
  252. new Fetcher<String, QuotaState>() {
  253. @Override
  254. public Get makeGet(final Map.Entry<String, QuotaState> entry) {
  255. return QuotaUtil.makeGetForRegionServerQuotas(entry.getKey());
  256. }
  257. @Override
  258. public Map<String, QuotaState> fetchEntries(final List<Get> gets) throws IOException {
  259. return QuotaUtil.fetchRegionServerQuotas(rsServices.getConnection(), gets);
  260. }
  261. });
  262. }
  263. private void fetchExceedThrottleQuota() {
  264. try {
  265. QuotaCache.this.exceedThrottleQuotaEnabled =
  266. QuotaUtil.isExceedThrottleQuotaEnabled(rsServices.getConnection());
  267. } catch (IOException e) {
  268. LOG.warn("Unable to read if exceed throttle quota enabled from quota table", e);
  269. }
  270. }
  271. private <K, V extends QuotaState> void fetch(final String type,
  272. final ConcurrentMap<K, V> quotasMap, final Fetcher<K, V> fetcher) {
  273. long now = EnvironmentEdgeManager.currentTime();
  274. long refreshPeriod = getPeriod();
  275. long evictPeriod = refreshPeriod * EVICT_PERIOD_FACTOR;
  276. // Find the quota entries to update
  277. List<Get> gets = new ArrayList<>();
  278. List<K> toRemove = new ArrayList<>();
  279. for (Map.Entry<K, V> entry: quotasMap.entrySet()) {
  280. long lastUpdate = entry.getValue().getLastUpdate();
  281. long lastQuery = entry.getValue().getLastQuery();
  282. if (lastQuery > 0 && (now - lastQuery) >= evictPeriod) {
  283. toRemove.add(entry.getKey());
  284. } else if (TEST_FORCE_REFRESH || (now - lastUpdate) >= refreshPeriod) {
  285. gets.add(fetcher.makeGet(entry));
  286. }
  287. }
  288. for (final K key: toRemove) {
  289. if (LOG.isTraceEnabled()) {
  290. LOG.trace("evict " + type + " key=" + key);
  291. }
  292. quotasMap.remove(key);
  293. }
  294. // fetch and update the quota entries
  295. if (!gets.isEmpty()) {
  296. try {
  297. for (Map.Entry<K, V> entry: fetcher.fetchEntries(gets).entrySet()) {
  298. V quotaInfo = quotasMap.putIfAbsent(entry.getKey(), entry.getValue());
  299. if (quotaInfo != null) {
  300. quotaInfo.update(entry.getValue());
  301. }
  302. if (LOG.isTraceEnabled()) {
  303. LOG.trace("refresh " + type + " key=" + entry.getKey() + " quotas=" + quotaInfo);
  304. }
  305. }
  306. } catch (IOException e) {
  307. LOG.warn("Unable to read " + type + " from quota table", e);
  308. }
  309. }
  310. }
  311. /**
  312. * Update quota factors which is used to divide cluster scope quota into machine scope quota
  313. *
  314. * For user/namespace/user over namespace quota, use [1 / RSNum] as machine factor.
  315. * For table/user over table quota, use [1 / TotalTableRegionNum * MachineTableRegionNum]
  316. * as machine factor.
  317. */
  318. private void updateQuotaFactors() {
  319. // Update machine quota factor
  320. try {
  321. int rsSize = rsServices.getConnection().getAdmin()
  322. .getClusterMetrics(EnumSet.of(Option.SERVERS_NAME)).getServersName().size();
  323. if (rsSize != 0) {
  324. // TODO if use rs group, the cluster limit should be shared by the rs group
  325. machineQuotaFactor = 1.0 / rsSize;
  326. }
  327. } catch (IOException e) {
  328. LOG.warn("Get live region servers failed", e);
  329. }
  330. // Update table machine quota factors
  331. for (TableName tableName : tableQuotaCache.keySet()) {
  332. double factor = 1;
  333. try {
  334. long regionSize =
  335. MetaTableAccessor.getTableRegions(rsServices.getConnection(), tableName, true)
  336. .stream().filter(regionInfo -> !regionInfo.isOffline()).count();
  337. if (regionSize == 0) {
  338. factor = 0;
  339. } else {
  340. int localRegionSize = rsServices.getRegions(tableName).size();
  341. factor = 1.0 * localRegionSize / regionSize;
  342. }
  343. } catch (IOException e) {
  344. LOG.warn("Get table regions failed: {}", tableName, e);
  345. }
  346. tableMachineQuotaFactors.put(tableName, factor);
  347. }
  348. }
  349. }
  350. static interface Fetcher<Key, Value> {
  351. Get makeGet(Map.Entry<Key, Value> entry);
  352. Map<Key, Value> fetchEntries(List<Get> gets) throws IOException;
  353. }
  354. }