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       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 }