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.util.concurrent.TimeUnit;
15
16 import org.apache.hadoop.hbase.classification.InterfaceAudience;
17 import org.apache.hadoop.hbase.classification.InterfaceStability;
18
19 import com.google.common.annotations.VisibleForTesting;
20
21 /**
22 * Simple rate limiter.
23 *
24 * Usage Example:
25 * // At this point you have a unlimited resource limiter
26 * RateLimiter limiter = new AverageIntervalRateLimiter();
27 * or new FixedIntervalRateLimiter();
28 * limiter.set(10, TimeUnit.SECONDS); // set 10 resources/sec
29 *
30 * while (true) {
31 * // call canExecute before performing resource consuming operation
32 * bool canExecute = limiter.canExecute();
33 * // If there are no available resources, wait until one is available
34 * if (!canExecute) Thread.sleep(limiter.waitInterval());
35 * // ...execute the work and consume the resource...
36 * limiter.consume();
37 * }
38 */
39 @InterfaceAudience.Private
40 @InterfaceStability.Evolving
41 public abstract class RateLimiter {
42 public static final String QUOTA_RATE_LIMITER_CONF_KEY = "hbase.quota.rate.limiter";
43 private long tunit = 1000; // Timeunit factor for translating to ms.
44 private long limit = Long.MAX_VALUE; // The max value available resource units can be refilled to.
45 private long avail = Long.MAX_VALUE; // Currently available resource units
46
47 /**
48 * Refill the available units w.r.t the elapsed time.
49 * @param limit Maximum available resource units that can be refilled to.
50 * @return how many resource units may be refilled ?
51 */
52 abstract long refill(long limit);
53
54 /**
55 * Time in milliseconds to wait for before requesting to consume 'amount' resource.
56 * @param limit Maximum available resource units that can be refilled to.
57 * @param available Currently available resource units
58 * @param amount Resources for which time interval to calculate for
59 * @return estimate of the ms required to wait before being able to provide 'amount' resources.
60 */
61 abstract long getWaitInterval(long limit, long available, long amount);
62
63
64 /**
65 * Set the RateLimiter max available resources and refill period.
66 * @param limit The max value available resource units can be refilled to.
67 * @param timeUnit Timeunit factor for translating to ms.
68 */
69 public void set(final long limit, final TimeUnit timeUnit) {
70 switch (timeUnit) {
71 case MILLISECONDS:
72 tunit = 1;
73 break;
74 case SECONDS:
75 tunit = 1000;
76 break;
77 case MINUTES:
78 tunit = 60 * 1000;
79 break;
80 case HOURS:
81 tunit = 60 * 60 * 1000;
82 break;
83 case DAYS:
84 tunit = 24 * 60 * 60 * 1000;
85 break;
86 default:
87 throw new RuntimeException("Unsupported " + timeUnit.name() + " TimeUnit.");
88 }
89 this.limit = limit;
90 this.avail = limit;
91 }
92
93 public String toString() {
94 String rateLimiter = this.getClass().getSimpleName();
95 if (limit == Long.MAX_VALUE) {
96 return rateLimiter + "(Bypass)";
97 }
98 return rateLimiter + "(avail=" + avail + " limit=" + limit + " tunit=" + tunit + ")";
99 }
100
101 /**
102 * Sets the current instance of RateLimiter to a new values.
103 *
104 * if current limit is smaller than the new limit, bump up the available resources.
105 * Otherwise allow clients to use up the previously available resources.
106 */
107 public synchronized void update(final RateLimiter other) {
108 this.tunit = other.tunit;
109 if (this.limit < other.limit) {
110 this.avail += (other.limit - this.limit);
111 }
112 this.limit = other.limit;
113 }
114
115 public synchronized boolean isBypass() {
116 return limit == Long.MAX_VALUE;
117 }
118
119 public synchronized long getLimit() {
120 return limit;
121 }
122
123 public synchronized long getAvailable() {
124 return avail;
125 }
126
127 protected long getTimeUnitInMillis() {
128 return tunit;
129 }
130
131 /**
132 * Is there at least one resource available to allow execution?
133 * @return true if there is at least one resource available, otherwise false
134 */
135 public boolean canExecute() {
136 return canExecute(1);
137 }
138
139 /**
140 * Are there enough available resources to allow execution?
141 * @param amount the number of required resources
142 * @return true if there are enough available resources, otherwise false
143 */
144 public synchronized boolean canExecute(final long amount) {
145 long refillAmount = refill(limit);
146 if (refillAmount == 0 && avail < amount) {
147 return false;
148 }
149 // check for positive overflow
150 if (avail <= Long.MAX_VALUE - refillAmount) {
151 avail = Math.max(0, Math.min(avail + refillAmount, limit));
152 } else {
153 avail = Math.max(0, limit);
154 }
155 if (avail >= amount) {
156 return true;
157 }
158 return false;
159 }
160
161 /**
162 * consume one available unit.
163 */
164 public void consume() {
165 consume(1);
166 }
167
168 /**
169 * consume amount available units.
170 * @param amount the number of units to consume
171 */
172 public synchronized void consume(final long amount) {
173 this.avail -= amount;
174 if (this.avail < 0) {
175 this.avail = 0;
176 }
177 }
178
179 /**
180 * @return estimate of the ms required to wait before being able to provide 1 resource.
181 */
182 public long waitInterval() {
183 return waitInterval(1);
184 }
185
186 /**
187 * @return estimate of the ms required to wait before being able to provide "amount" resources.
188 */
189 public synchronized long waitInterval(final long amount) {
190 // TODO Handle over quota?
191 return (amount <= avail) ? 0 : getWaitInterval(limit, avail, amount);
192 }
193
194 // These two method are for strictly testing purpose only
195 @VisibleForTesting
196 public abstract void setNextRefillTime(long nextRefillTime);
197
198 @VisibleForTesting
199 public abstract long getNextRefillTime();
200 }