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