1
2
3
4
5
6
7
8
9
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
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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;
44 private long limit = Long.MAX_VALUE;
45 private long avail = Long.MAX_VALUE;
46
47
48
49
50
51
52 abstract long refill(long limit);
53
54
55
56
57
58
59
60
61 abstract long getWaitInterval(long limit, long available, long amount);
62
63
64
65
66
67
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
103
104
105
106
107 public synchronized void update(final RateLimiter other) {
108 this.tunit = other.tunit;
109 if (this.limit < other.limit) {
110
111
112 long diff = other.limit - this.limit;
113 if (this.avail <= Long.MAX_VALUE - diff) {
114 this.avail += diff;
115 this.avail = Math.min(this.avail, other.limit);
116 } else {
117 this.avail = other.limit;
118 }
119 }
120 this.limit = other.limit;
121 }
122
123 public synchronized boolean isBypass() {
124 return limit == Long.MAX_VALUE;
125 }
126
127 public synchronized long getLimit() {
128 return limit;
129 }
130
131 public synchronized long getAvailable() {
132 return avail;
133 }
134
135 protected long getTimeUnitInMillis() {
136 return tunit;
137 }
138
139
140
141
142
143 public boolean canExecute() {
144 return canExecute(1);
145 }
146
147
148
149
150
151
152 public synchronized boolean canExecute(final long amount) {
153 if (isBypass()) {
154 return true;
155 }
156
157 long refillAmount = refill(limit);
158 if (refillAmount == 0 && avail < amount) {
159 return false;
160 }
161
162 if (avail <= Long.MAX_VALUE - refillAmount) {
163 avail = Math.max(0, Math.min(avail + refillAmount, limit));
164 } else {
165 avail = Math.max(0, limit);
166 }
167 if (avail >= amount) {
168 return true;
169 }
170 return false;
171 }
172
173
174
175
176 public void consume() {
177 consume(1);
178 }
179
180
181
182
183
184 public synchronized void consume(final long amount) {
185
186 if (isBypass()) {
187 return;
188 }
189
190 if (amount >= 0 ) {
191 this.avail -= amount;
192 if (this.avail < 0) {
193 this.avail = 0;
194 }
195 } else {
196 if (this.avail <= Long.MAX_VALUE + amount) {
197 this.avail -= amount;
198 this.avail = Math.min(this.avail, this.limit);
199 } else {
200 this.avail = this.limit;
201 }
202 }
203 }
204
205
206
207
208 public long waitInterval() {
209 return waitInterval(1);
210 }
211
212
213
214
215 public synchronized long waitInterval(final long amount) {
216
217 return (amount <= avail) ? 0 : getWaitInterval(limit, avail, amount);
218 }
219
220
221 @VisibleForTesting
222 public abstract void setNextRefillTime(long nextRefillTime);
223
224 @VisibleForTesting
225 public abstract long getNextRefillTime();
226 }