001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.quotas;
019
020import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
021import org.apache.yetus.audience.InterfaceAudience;
022import org.apache.yetus.audience.InterfaceStability;
023
024import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
025
026/**
027 * With this limiter resources will be refilled only after a fixed interval of time.
028 */
029@InterfaceAudience.Private
030@InterfaceStability.Evolving
031public class FixedIntervalRateLimiter extends RateLimiter {
032
033  /**
034   * The FixedIntervalRateLimiter can be harsh from a latency/backoff perspective, which makes it
035   * difficult to fully and consistently utilize a quota allowance. By configuring the
036   * {@link #RATE_LIMITER_REFILL_INTERVAL_MS} to a lower value you will encourage the rate limiter
037   * to throw smaller wait intervals for requests which may be fulfilled in timeframes shorter than
038   * the quota's full interval. For example, if you're saturating a 100MB/sec read IO quota with a
039   * ton of tiny gets, then configuring this to a value like 100ms will ensure that your retry
040   * backoffs approach ~100ms, rather than 1sec. Be careful not to configure this too low, or you
041   * may produce a dangerous amount of retry volume.
042   */
043  public static final String RATE_LIMITER_REFILL_INTERVAL_MS =
044    "hbase.quota.rate.limiter.refill.interval.ms";
045
046  private long nextRefillTime = -1L;
047  private final long refillInterval;
048
049  public FixedIntervalRateLimiter() {
050    this(DEFAULT_TIME_UNIT);
051  }
052
053  public FixedIntervalRateLimiter(long refillInterval) {
054    super();
055    Preconditions.checkArgument(getTimeUnitInMillis() >= refillInterval,
056      String.format("Refill interval %s must be less than or equal to TimeUnit millis %s",
057        refillInterval, getTimeUnitInMillis()));
058    this.refillInterval = refillInterval;
059  }
060
061  @Override
062  public long refill(long limit) {
063    final long now = EnvironmentEdgeManager.currentTime();
064    if (nextRefillTime == -1) {
065      nextRefillTime = now + refillInterval;
066      return limit;
067    }
068    if (now < nextRefillTime) {
069      return 0;
070    }
071    long diff = refillInterval + now - nextRefillTime;
072    long refills = diff / refillInterval;
073    nextRefillTime = now + refillInterval;
074    long refillAmount = refills * getRefillIntervalAdjustedLimit(limit);
075    return Math.min(limit, refillAmount);
076  }
077
078  @Override
079  public long getWaitInterval(long limit, long available, long amount) {
080    // adjust the limit based on the refill interval
081    limit = getRefillIntervalAdjustedLimit(limit);
082
083    if (nextRefillTime == -1) {
084      return 0;
085    }
086    final long now = EnvironmentEdgeManager.currentTime();
087    final long refillTime = nextRefillTime;
088    long diff = amount - available;
089    // We will add limit at next interval. If diff is less than that limit, the wait interval
090    // is just time between now and then.
091    long nextRefillInterval = refillTime - now;
092    if (diff <= limit) {
093      return nextRefillInterval;
094    }
095
096    // Otherwise, we need to figure out how many refills are needed.
097    // There will be one at nextRefillInterval, and then some number of extra refills.
098    // Division will round down if not even, so we can just add that to our next interval
099    long extraRefillsNecessary = diff / limit;
100    // If it's even, subtract one since that will be covered by nextRefillInterval
101    if (diff % limit == 0) {
102      extraRefillsNecessary--;
103    }
104    return nextRefillInterval + (extraRefillsNecessary * refillInterval);
105  }
106
107  private long getRefillIntervalAdjustedLimit(long limit) {
108    return (long) Math.ceil(refillInterval / (double) getTimeUnitInMillis() * limit);
109  }
110
111  // This method is for strictly testing purpose only
112  @Override
113  public void setNextRefillTime(long nextRefillTime) {
114    this.nextRefillTime = nextRefillTime;
115  }
116
117  @Override
118  public long getNextRefillTime() {
119    return this.nextRefillTime;
120  }
121}