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  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       // If avail is capped to this.limit, it will never overflow,
111       // otherwise, avail may overflow, just be careful here.
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    * Is there at least one resource available to allow execution?
141    * @return true if there is at least one resource available, otherwise false
142    */
143   public boolean canExecute() {
144     return canExecute(1);
145   }
146 
147   /**
148    * Are there enough available resources to allow execution?
149    * @param amount the number of required resources, a non-negative number
150    * @return true if there are enough available resources, otherwise false
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     // check for positive overflow
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    * consume one available unit.
175    */
176   public void consume() {
177     consume(1);
178   }
179 
180   /**
181    * consume amount available units, amount could be a negative number
182    * @param amount the number of units to consume
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    * @return estimate of the ms required to wait before being able to provide 1 resource.
207    */
208   public long waitInterval() {
209     return waitInterval(1);
210   }
211 
212   /**
213    * @return estimate of the ms required to wait before being able to provide "amount" resources.
214    */
215   public synchronized long waitInterval(final long amount) {
216     // TODO Handle over quota?
217     return (amount <= avail) ? 0 : getWaitInterval(limit, avail, amount);
218   }
219 
220   // These two method are for strictly testing purpose only
221   @VisibleForTesting
222   public abstract void setNextRefillTime(long nextRefillTime);
223 
224   @VisibleForTesting
225   public abstract long getNextRefillTime();
226 }