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.util; 019 020import java.util.concurrent.ThreadLocalRandom; 021import java.util.concurrent.TimeUnit; 022import org.apache.yetus.audience.InterfaceAudience; 023import org.slf4j.Logger; 024import org.slf4j.LoggerFactory; 025 026import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; 027 028/** 029 * Operation retry accounting. Use to calculate wait period, 030 * {@link #getBackoffTimeAndIncrementAttempts()}}, or for performing wait, 031 * {@link #sleepUntilNextRetry()}, in accordance with a {@link RetryConfig}, initial settings, and a 032 * Retry Policy, (See org.apache.hadoop.io.retry.RetryPolicy). Like <a 033 * href=https://github.com/rholder/guava-retrying>guava-retrying</a>. 034 * @since 0.92.0 035 * @see RetryCounterFactory 036 */ 037@InterfaceAudience.Private 038public class RetryCounter { 039 /** 040 * Configuration for a retry counter 041 */ 042 public static class RetryConfig { 043 private int maxAttempts; 044 private long sleepInterval; 045 private long maxSleepTime; 046 private TimeUnit timeUnit; 047 private BackoffPolicy backoffPolicy; 048 private float jitter; 049 050 private static final BackoffPolicy DEFAULT_BACKOFF_POLICY = new ExponentialBackoffPolicy(); 051 052 public RetryConfig() { 053 maxAttempts = 1; 054 sleepInterval = 1000; 055 maxSleepTime = -1; 056 timeUnit = TimeUnit.MILLISECONDS; 057 backoffPolicy = DEFAULT_BACKOFF_POLICY; 058 jitter = 0.0f; 059 } 060 061 public RetryConfig(int maxAttempts, long sleepInterval, long maxSleepTime, TimeUnit timeUnit, 062 BackoffPolicy backoffPolicy) { 063 this.maxAttempts = maxAttempts; 064 this.sleepInterval = sleepInterval; 065 this.maxSleepTime = maxSleepTime; 066 this.timeUnit = timeUnit; 067 this.backoffPolicy = backoffPolicy; 068 } 069 070 public RetryConfig setBackoffPolicy(BackoffPolicy backoffPolicy) { 071 this.backoffPolicy = backoffPolicy; 072 return this; 073 } 074 075 public RetryConfig setMaxAttempts(int maxAttempts) { 076 this.maxAttempts = maxAttempts; 077 return this; 078 } 079 080 public RetryConfig setMaxSleepTime(long maxSleepTime) { 081 this.maxSleepTime = maxSleepTime; 082 return this; 083 } 084 085 public RetryConfig setSleepInterval(long sleepInterval) { 086 this.sleepInterval = sleepInterval; 087 return this; 088 } 089 090 public RetryConfig setTimeUnit(TimeUnit timeUnit) { 091 this.timeUnit = timeUnit; 092 return this; 093 } 094 095 public RetryConfig setJitter(float jitter) { 096 Preconditions.checkArgument(jitter >= 0.0f && jitter < 1.0f, 097 "Invalid jitter: %s, should be in range [0.0, 1.0)", jitter); 098 this.jitter = jitter; 099 return this; 100 } 101 102 public int getMaxAttempts() { 103 return maxAttempts; 104 } 105 106 public long getMaxSleepTime() { 107 return maxSleepTime; 108 } 109 110 public long getSleepInterval() { 111 return sleepInterval; 112 } 113 114 public TimeUnit getTimeUnit() { 115 return timeUnit; 116 } 117 118 public float getJitter() { 119 return jitter; 120 } 121 122 public BackoffPolicy getBackoffPolicy() { 123 return backoffPolicy; 124 } 125 } 126 127 private static long addJitter(long interval, float jitter) { 128 long jitterInterval = (long) (interval * ThreadLocalRandom.current().nextFloat() * jitter); 129 return interval + jitterInterval; 130 } 131 132 /** 133 * Policy for calculating sleeping intervals between retry attempts 134 */ 135 public static class BackoffPolicy { 136 public long getBackoffTime(RetryConfig config, int attempts) { 137 return addJitter(config.getSleepInterval(), config.getJitter()); 138 } 139 } 140 141 public static class ExponentialBackoffPolicy extends BackoffPolicy { 142 @Override 143 public long getBackoffTime(RetryConfig config, int attempts) { 144 long backoffTime = (long) (config.getSleepInterval() * Math.pow(2, attempts)); 145 return addJitter(backoffTime, config.getJitter()); 146 } 147 } 148 149 public static class ExponentialBackoffPolicyWithLimit extends ExponentialBackoffPolicy { 150 @Override 151 public long getBackoffTime(RetryConfig config, int attempts) { 152 long backoffTime = super.getBackoffTime(config, attempts); 153 return config.getMaxSleepTime() > 0 154 ? Math.min(backoffTime, config.getMaxSleepTime()) 155 : backoffTime; 156 } 157 } 158 159 private static final Logger LOG = LoggerFactory.getLogger(RetryCounter.class); 160 161 private RetryConfig retryConfig; 162 private int attempts; 163 164 public RetryCounter(int maxAttempts, long sleepInterval, TimeUnit timeUnit) { 165 this(new RetryConfig(maxAttempts, sleepInterval, -1, timeUnit, new ExponentialBackoffPolicy())); 166 } 167 168 public RetryCounter(RetryConfig retryConfig) { 169 this.attempts = 0; 170 this.retryConfig = retryConfig; 171 } 172 173 public int getMaxAttempts() { 174 return retryConfig.getMaxAttempts(); 175 } 176 177 /** 178 * Sleep for a back off time as supplied by the backoff policy, and increases the attempts 179 */ 180 public void sleepUntilNextRetry() throws InterruptedException { 181 int attempts = getAttemptTimes(); 182 long sleepTime = getBackoffTime(); 183 LOG.trace("Sleeping {} ms before retry #{}...", sleepTime, attempts); 184 retryConfig.getTimeUnit().sleep(sleepTime); 185 useRetry(); 186 } 187 188 public boolean shouldRetry() { 189 return attempts < retryConfig.getMaxAttempts(); 190 } 191 192 public void useRetry() { 193 attempts++; 194 } 195 196 public boolean isRetry() { 197 return attempts > 0; 198 } 199 200 public int getAttemptTimes() { 201 return attempts; 202 } 203 204 public long getBackoffTime() { 205 return this.retryConfig.backoffPolicy.getBackoffTime(this.retryConfig, getAttemptTimes()); 206 } 207 208 public long getBackoffTimeAndIncrementAttempts() { 209 long backoffTime = getBackoffTime(); 210 useRetry(); 211 return backoffTime; 212 } 213}