1
2
3
4
5
6
7
8
9
10
11
12 package org.apache.hadoop.hbase.quotas;
13
14 import java.io.IOException;
15 import java.util.ArrayList;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Set;
19 import java.util.concurrent.ConcurrentHashMap;
20
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.apache.hadoop.conf.Configuration;
24 import org.apache.hadoop.hbase.ScheduledChore;
25 import org.apache.hadoop.hbase.Stoppable;
26 import org.apache.hadoop.hbase.TableName;
27 import org.apache.hadoop.hbase.classification.InterfaceAudience;
28 import org.apache.hadoop.hbase.classification.InterfaceStability;
29 import org.apache.hadoop.hbase.client.Get;
30 import org.apache.hadoop.hbase.regionserver.RegionServerServices;
31 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
32 import org.apache.hadoop.security.UserGroupInformation;
33
34
35
36
37
38
39
40
41 @InterfaceAudience.Private
42 @InterfaceStability.Evolving
43 public class QuotaCache implements Stoppable {
44 private static final Log LOG = LogFactory.getLog(QuotaCache.class);
45
46 public static final String REFRESH_CONF_KEY = "hbase.quota.refresh.period";
47 private static final int REFRESH_DEFAULT_PERIOD = 5 * 60000;
48 private static final int EVICT_PERIOD_FACTOR = 5;
49
50
51 private static boolean TEST_FORCE_REFRESH = false;
52
53 private final ConcurrentHashMap<String, QuotaState> namespaceQuotaCache =
54 new ConcurrentHashMap<String, QuotaState>();
55 private final ConcurrentHashMap<TableName, QuotaState> tableQuotaCache =
56 new ConcurrentHashMap<TableName, QuotaState>();
57 private final ConcurrentHashMap<String, UserQuotaState> userQuotaCache =
58 new ConcurrentHashMap<String, UserQuotaState>();
59 private final RegionServerServices rsServices;
60
61 private QuotaRefresherChore refreshChore;
62 private boolean stopped = true;
63
64 public QuotaCache(final RegionServerServices rsServices) {
65 this.rsServices = rsServices;
66 }
67
68 public void start() throws IOException {
69 stopped = false;
70
71
72 Configuration conf = rsServices.getConfiguration();
73 int period = conf.getInt(REFRESH_CONF_KEY, REFRESH_DEFAULT_PERIOD);
74 refreshChore = new QuotaRefresherChore(period, this);
75 rsServices.getChoreService().scheduleChore(refreshChore);
76 }
77
78 @Override
79 public void stop(final String why) {
80 stopped = true;
81 }
82
83 @Override
84 public boolean isStopped() {
85 return stopped;
86 }
87
88
89
90
91
92
93
94 public QuotaLimiter getUserLimiter(final UserGroupInformation ugi, final TableName table) {
95 if (table.isSystemTable()) {
96 return NoopQuotaLimiter.get();
97 }
98 return getUserQuotaState(ugi).getTableLimiter(table);
99 }
100
101
102
103
104
105
106 public UserQuotaState getUserQuotaState(final UserGroupInformation ugi) {
107 String key = ugi.getShortUserName();
108 UserQuotaState quotaInfo = userQuotaCache.get(key);
109 if (quotaInfo == null) {
110 quotaInfo = new UserQuotaState();
111 if (userQuotaCache.putIfAbsent(key, quotaInfo) == null) {
112 triggerCacheRefresh();
113 }
114 }
115 return quotaInfo;
116 }
117
118
119
120
121
122
123 public QuotaLimiter getTableLimiter(final TableName table) {
124 return getQuotaState(this.tableQuotaCache, table).getGlobalLimiter();
125 }
126
127
128
129
130
131
132 public QuotaLimiter getNamespaceLimiter(final String namespace) {
133 return getQuotaState(this.namespaceQuotaCache, namespace).getGlobalLimiter();
134 }
135
136
137
138
139
140 private <K> QuotaState
141 getQuotaState(final ConcurrentHashMap<K, QuotaState> quotasMap, final K key) {
142 QuotaState quotaInfo = quotasMap.get(key);
143 if (quotaInfo == null) {
144 quotaInfo = new QuotaState();
145 if (quotasMap.putIfAbsent(key, quotaInfo) == null) {
146 triggerCacheRefresh();
147 }
148 }
149 return quotaInfo;
150 }
151
152 void triggerCacheRefresh() {
153 refreshChore.triggerNow();
154 }
155
156 long getLastUpdate() {
157 return refreshChore.lastUpdate;
158 }
159
160 Map<String, QuotaState> getNamespaceQuotaCache() {
161 return namespaceQuotaCache;
162 }
163
164 Map<TableName, QuotaState> getTableQuotaCache() {
165 return tableQuotaCache;
166 }
167
168 Map<String, UserQuotaState> getUserQuotaCache() {
169 return userQuotaCache;
170 }
171
172 public static boolean isTEST_FORCE_REFRESH() {
173 return TEST_FORCE_REFRESH;
174 }
175
176 public static void setTEST_FORCE_REFRESH(boolean tEST_FORCE_REFRESH) {
177 TEST_FORCE_REFRESH = tEST_FORCE_REFRESH;
178 }
179
180
181 private class QuotaRefresherChore extends ScheduledChore {
182 private long lastUpdate = 0;
183
184 public QuotaRefresherChore(final int period, final Stoppable stoppable) {
185 super("QuotaRefresherChore", stoppable, period);
186 }
187
188 @Override
189 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "GC_UNRELATED_TYPES",
190 justification = "I do not understand why the complaints, it looks good to me -- FIX")
191 protected void chore() {
192
193 for (TableName table : QuotaCache.this.rsServices.getOnlineTables()) {
194 if (table.isSystemTable()) continue;
195 if (!QuotaCache.this.tableQuotaCache.containsKey(table)) {
196 QuotaCache.this.tableQuotaCache.putIfAbsent(table, new QuotaState());
197 }
198 String ns = table.getNamespaceAsString();
199 if (!QuotaCache.this.namespaceQuotaCache.containsKey(ns)) {
200 QuotaCache.this.namespaceQuotaCache.putIfAbsent(ns, new QuotaState());
201 }
202 }
203
204 fetchNamespaceQuotaState();
205 fetchTableQuotaState();
206 fetchUserQuotaState();
207 lastUpdate = EnvironmentEdgeManager.currentTime();
208 }
209
210 private void fetchNamespaceQuotaState() {
211 fetch("namespace", QuotaCache.this.namespaceQuotaCache, new Fetcher<String, QuotaState>() {
212 @Override
213 public Get makeGet(final Map.Entry<String, QuotaState> entry) {
214 return QuotaUtil.makeGetForNamespaceQuotas(entry.getKey());
215 }
216
217 @Override
218 public Map<String, QuotaState> fetchEntries(final List<Get> gets) throws IOException {
219 return QuotaUtil.fetchNamespaceQuotas(rsServices.getConnection(), gets);
220 }
221 });
222 }
223
224 private void fetchTableQuotaState() {
225 fetch("table", QuotaCache.this.tableQuotaCache, new Fetcher<TableName, QuotaState>() {
226 @Override
227 public Get makeGet(final Map.Entry<TableName, QuotaState> entry) {
228 return QuotaUtil.makeGetForTableQuotas(entry.getKey());
229 }
230
231 @Override
232 public Map<TableName, QuotaState> fetchEntries(final List<Get> gets) throws IOException {
233 return QuotaUtil.fetchTableQuotas(rsServices.getConnection(), gets);
234 }
235 });
236 }
237
238 private void fetchUserQuotaState() {
239 final Set<String> namespaces = QuotaCache.this.namespaceQuotaCache.keySet();
240 final Set<TableName> tables = QuotaCache.this.tableQuotaCache.keySet();
241 fetch("user", QuotaCache.this.userQuotaCache, new Fetcher<String, UserQuotaState>() {
242 @Override
243 public Get makeGet(final Map.Entry<String, UserQuotaState> entry) {
244 return QuotaUtil.makeGetForUserQuotas(entry.getKey(), tables, namespaces);
245 }
246
247 @Override
248 public Map<String, UserQuotaState> fetchEntries(final List<Get> gets) throws IOException {
249 return QuotaUtil.fetchUserQuotas(rsServices.getConnection(), gets);
250 }
251 });
252 }
253
254 private <K, V extends QuotaState> void fetch(final String type,
255 final ConcurrentHashMap<K, V> quotasMap, final Fetcher<K, V> fetcher) {
256 long now = EnvironmentEdgeManager.currentTime();
257 long refreshPeriod = getPeriod();
258 long evictPeriod = refreshPeriod * EVICT_PERIOD_FACTOR;
259
260
261 List<Get> gets = new ArrayList<Get>();
262 List<K> toRemove = new ArrayList<K>();
263 for (Map.Entry<K, V> entry : quotasMap.entrySet()) {
264 long lastUpdate = entry.getValue().getLastUpdate();
265 long lastQuery = entry.getValue().getLastQuery();
266 if (lastQuery > 0 && (now - lastQuery) >= evictPeriod) {
267 toRemove.add(entry.getKey());
268 } else if (isTEST_FORCE_REFRESH() || (now - lastUpdate) >= refreshPeriod) {
269 gets.add(fetcher.makeGet(entry));
270 }
271 }
272
273 for (final K key : toRemove) {
274 if (LOG.isTraceEnabled()) {
275 LOG.trace("evict " + type + " key=" + key);
276 }
277 quotasMap.remove(key);
278 }
279
280
281 if (!gets.isEmpty()) {
282 try {
283 for (Map.Entry<K, V> entry : fetcher.fetchEntries(gets).entrySet()) {
284 V quotaInfo = quotasMap.putIfAbsent(entry.getKey(), entry.getValue());
285 if (quotaInfo != null) {
286 quotaInfo.update(entry.getValue());
287 }
288
289 if (LOG.isTraceEnabled()) {
290 LOG.trace("refresh " + type + " key=" + entry.getKey() + " quotas=" + quotaInfo);
291 }
292 }
293 } catch (IOException e) {
294 LOG.warn("Unable to read " + type + " from quota table", e);
295 }
296 }
297 }
298 }
299
300 static interface Fetcher<Key, Value> {
301 Get makeGet(Map.Entry<Key, Value> entry);
302
303 Map<Key, Value> fetchEntries(List<Get> gets) throws IOException;
304 }
305 }