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}