Make Jedis Cluster more likely to antirez's redis-rb-cluster

JedisClusterCommand

* improvements on connection error handling
** if based on slot connection throws connection related exception, retry to random node
** if we retry with random node, but all nodes are unreachable, throw JedisConnectionException without retry
** try to release connection whether connection is broken or not

* bug fix : if asking flag is on, and success this time, set asking flag to off

JedisClusterConnectionHandler

* have flexibility on initializing slots cache
** allow some nodes connection failure - skip
** if current node is success initializing slots cache, skip other nodes
** if current node failed to initialize slots cache, discard all discovered nodes and slots

* set nodes if node does not exist in nodes
** it restricts JedisPool to replace - prevent IllegalStateException : Returned object not currently part of this pool

JedisSlotBasedConnectionGuaranteedConnectionHandler

* getConnection (random connection)
** check all connections by random sequence
** always return valid connection (able to ping-pong)
** throw exception if all connections are invalid

* some refactoring
This commit is contained in:
Jungtaek Lim
2014-02-25 18:29:09 +09:00
parent e9cf469200
commit 882d662470
4 changed files with 196 additions and 41 deletions

View File

@@ -3,19 +3,18 @@ package redis.clients.jedis;
import redis.clients.jedis.exceptions.JedisAskDataException;
import redis.clients.jedis.exceptions.JedisClusterException;
import redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.exceptions.JedisMovedDataException;
import redis.clients.jedis.exceptions.JedisRedirectionException;
import redis.clients.util.JedisClusterCRC16;
public abstract class JedisClusterCommand<T> {
private boolean asking = false;
private JedisClusterConnectionHandler connectionHandler;
private int commandTimeout;
private int redirections;
// private boolean asking = false;
public JedisClusterCommand(JedisClusterConnectionHandler connectionHandler,
int timeout, int maxRedirections) {
this.connectionHandler = connectionHandler;
@@ -26,40 +25,80 @@ public abstract class JedisClusterCommand<T> {
public abstract T execute(Jedis connection);
public T run(String key) {
if (key == null) {
throw new JedisClusterException(
"No way to dispatch this command to Redis Cluster.");
}
return runWithRetries(key, this.redirections, false, false);
}
private T runWithRetries(String key, int redirections,
boolean tryRandomNode, boolean asking) {
if (redirections <= 0) {
throw new JedisClusterMaxRedirectionsException(
"Too many Cluster redirections?");
}
Jedis connection = null;
try {
if (key == null) {
throw new JedisClusterException(
"No way to dispatch this command to Redis Cluster.");
} else if (redirections == 0) {
throw new JedisClusterMaxRedirectionsException(
"Too many Cluster redirections?");
if (tryRandomNode) {
connection = connectionHandler.getConnection();
} else {
connection = connectionHandler
.getConnectionFromSlot(JedisClusterCRC16.getSlot(key));
}
connection = connectionHandler.getConnectionFromSlot(JedisClusterCRC16
.getSlot(key));
if (asking) {
// TODO: Pipeline asking with the original command to make it
// faster....
connection.asking();
// if asking success, reset asking flag
asking = false;
}
return execute(connection);
} catch (JedisConnectionException jce) {
if (tryRandomNode) {
// maybe all connection is down
throw jce;
}
releaseConnection(connection, true);
connection = null;
// retry with random connection
return runWithRetries(key, redirections--, true, asking);
} catch (JedisRedirectionException jre) {
return handleRedirection(jre, key);
if (jre instanceof JedisAskDataException) {
asking = true;
} else if (jre instanceof JedisMovedDataException) {
// TODO : In antirez's redis-rb-cluster implementation,
// it rebuilds cluster's slot and node cache
}
this.connectionHandler.assignSlotToNode(jre.getSlot(),
jre.getTargetNode());
releaseConnection(connection, false);
connection = null;
return runWithRetries(key, redirections - 1, false, asking);
} finally {
if (connection != null) {
releaseConnection(connection, false);
}
}
private void releaseConnection(Jedis connection, boolean broken) {
if (connection != null) {
if (broken) {
connectionHandler.returnBrokenConnection(connection);
} else {
connectionHandler.returnConnection(connection);
}
}
}
private T handleRedirection(JedisRedirectionException jre, String key) {
if (jre instanceof JedisAskDataException) {
asking = true;
}
redirections--;
this.connectionHandler.assignSlotToNode(jre.getSlot(),
jre.getTargetNode());
return run(key);
}
}