redis.clients.jedis.executors.RetryableCommandExecutor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jedis_preview Show documentation
Show all versions of jedis_preview Show documentation
Jedis is a blazingly small and sane Redis java client.
The newest version!
package redis.clients.jedis.executors;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.CommandObject;
import redis.clients.jedis.Connection;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.util.IOUtils;
import redis.clients.jedis.providers.ConnectionProvider;
public class RetryableCommandExecutor implements CommandExecutor {
private final Logger log = LoggerFactory.getLogger(getClass());
protected final ConnectionProvider provider;
protected final int maxAttempts;
protected final Duration maxTotalRetriesDuration;
public RetryableCommandExecutor(ConnectionProvider provider, int maxAttempts,
Duration maxTotalRetriesDuration) {
this.provider = provider;
this.maxAttempts = maxAttempts;
this.maxTotalRetriesDuration = maxTotalRetriesDuration;
}
@Override
public void close() {
IOUtils.closeQuietly(this.provider);
}
@Override
public final T executeCommand(CommandObject commandObject) {
Instant deadline = Instant.now().plus(maxTotalRetriesDuration);
int consecutiveConnectionFailures = 0;
JedisException lastException = null;
for (int attemptsLeft = this.maxAttempts; attemptsLeft > 0; attemptsLeft--) {
Connection connection = null;
try {
connection = provider.getConnection(commandObject.getArguments());
return execute(connection, commandObject);
} catch (JedisConnectionException jce) {
lastException = jce;
++consecutiveConnectionFailures;
log.debug("Failed connecting to Redis: {}", connection, jce);
// "- 1" because we just did one, but the attemptsLeft counter hasn't been decremented yet
boolean reset = handleConnectionProblem(attemptsLeft - 1, consecutiveConnectionFailures, deadline);
if (reset) {
consecutiveConnectionFailures = 0;
}
} finally {
if (connection != null) {
connection.close();
}
}
if (Instant.now().isAfter(deadline)) {
throw new JedisException("Retry deadline exceeded.");
}
}
JedisException maxAttemptsException = new JedisException("No more attempts left.");
maxAttemptsException.addSuppressed(lastException);
throw maxAttemptsException;
}
/**
* WARNING: This method is accessible for the purpose of testing.
* This should not be used or overriden.
*/
protected T execute(Connection connection, CommandObject commandObject) {
return connection.executeCommand(commandObject);
}
/**
* Related values should be reset if TRUE
is returned.
*
* @param attemptsLeft
* @param consecutiveConnectionFailures
* @param doneDeadline
* @return true - if some actions are taken
*
false - if no actions are taken
*/
private boolean handleConnectionProblem(int attemptsLeft, int consecutiveConnectionFailures, Instant doneDeadline) {
if (consecutiveConnectionFailures < 2) {
return false;
}
sleep(getBackoffSleepMillis(attemptsLeft, doneDeadline));
return true;
}
private static long getBackoffSleepMillis(int attemptsLeft, Instant deadline) {
if (attemptsLeft <= 0) {
return 0;
}
long millisLeft = Duration.between(Instant.now(), deadline).toMillis();
if (millisLeft < 0) {
throw new JedisException("Retry deadline exceeded.");
}
return millisLeft / (attemptsLeft * (attemptsLeft + 1));
}
/**
* WARNING: This method is accessible for the purpose of testing.
* This should not be used or overriden.
*/
protected void sleep(long sleepMillis) {
try {
TimeUnit.MILLISECONDS.sleep(sleepMillis);
} catch (InterruptedException e) {
throw new JedisException(e);
}
}
}