sentinels = null;
private JedisClientConfig sentinelClientConfig = null;
+ private ReadFrom readFrom = ReadFrom.UPSTREAM;
+
+ private ReadOnlyCommands.ReadOnlyPredicate readOnlyPredicate = ReadOnlyCommands.asPredicate();
+
/**
* Sets the master name for the Redis Sentinel configuration.
*
@@ -47,6 +52,34 @@ public SentinelClientBuilder sentinels(Set sentinels) {
return this;
}
+ /**
+ * Sets the readFrom.
+ *
+ * It is used to specify the policy preference of which nodes the client should read data from. It
+ * defines which type of node the client should prioritize reading data from when there are
+ * multiple Redis instances (such as master nodes and slave nodes) available in the Redis Sentinel
+ * environment.
+ * @param readFrom the read preferences
+ * @return this builder
+ */
+ public SentinelClientBuilder readForm(ReadFrom readFrom) {
+ this.readFrom = readFrom;
+ return this;
+ }
+
+ /**
+ * Sets the readOnlyPredicate.
+ *
+ * Check a Redis command is a read request.
+ * @param readOnlyPredicate
+ * @return this builder
+ */
+ public SentinelClientBuilder readOnlyPredicate(
+ ReadOnlyCommands.ReadOnlyPredicate readOnlyPredicate) {
+ this.readOnlyPredicate = readOnlyPredicate;
+ return this;
+ }
+
/**
* Sets the client configuration for Sentinel connections.
*
@@ -68,7 +101,8 @@ protected SentinelClientBuilder self() {
@Override
protected ConnectionProvider createDefaultConnectionProvider() {
return new SentineledConnectionProvider(this.masterName, this.clientConfig, this.cache,
- this.poolConfig, this.sentinels, this.sentinelClientConfig);
+ this.poolConfig, this.sentinels, this.sentinelClientConfig, this.readFrom,
+ this.readOnlyPredicate);
}
@Override
diff --git a/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java
index 811fa4ed66..7d8b72f08f 100644
--- a/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java
+++ b/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java
@@ -21,14 +21,25 @@
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisPubSub;
+import redis.clients.jedis.ReadFrom;
import redis.clients.jedis.annots.Experimental;
import redis.clients.jedis.csc.Cache;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.util.IOUtils;
+import redis.clients.jedis.util.ReadOnlyCommands;
import redis.clients.jedis.util.Pool;
public class SentineledConnectionProvider implements ConnectionProvider {
+ class PoolInfo {
+ public String host;
+ public ConnectionPool pool;
+
+ public PoolInfo(String host, ConnectionPool pool) {
+ this.host = host;
+ this.pool = pool;
+ }
+ }
private static final Logger LOG = LoggerFactory.getLogger(SentineledConnectionProvider.class);
@@ -52,8 +63,18 @@ public class SentineledConnectionProvider implements ConnectionProvider {
private final long subscribeRetryWaitTimeMillis;
+ private final ReadFrom readFrom;
+
+ private ReadOnlyCommands.ReadOnlyPredicate READ_ONLY_COMMANDS;
+
private final Lock initPoolLock = new ReentrantLock(true);
+ private final List slavePools = new ArrayList<>();
+
+ private final Lock slavePoolsLock = new ReentrantLock(true);
+
+ private int poolIndex;
+
public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,
Set sentinels, final JedisClientConfig sentinelClientConfig) {
this(masterName, masterClientConfig, null, null, sentinels, sentinelClientConfig);
@@ -72,26 +93,46 @@ public SentineledConnectionProvider(String masterName, final JedisClientConfig m
DEFAULT_SUBSCRIBE_RETRY_WAIT_TIME_MILLIS);
}
+ public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,
+ final GenericObjectPoolConfig poolConfig,
+ Set sentinels, final JedisClientConfig sentinelClientConfig, ReadFrom readFrom) {
+ this(masterName, masterClientConfig, null, poolConfig, sentinels, sentinelClientConfig,
+ DEFAULT_SUBSCRIBE_RETRY_WAIT_TIME_MILLIS, readFrom, ReadOnlyCommands.asPredicate());
+ }
+
+ public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,
+ final GenericObjectPoolConfig poolConfig,
+ Set sentinels, final JedisClientConfig sentinelClientConfig, ReadFrom readFrom,
+ ReadOnlyCommands.ReadOnlyPredicate readOnlyPredicate) {
+ this(masterName, masterClientConfig, null, poolConfig, sentinels, sentinelClientConfig,
+ DEFAULT_SUBSCRIBE_RETRY_WAIT_TIME_MILLIS, readFrom, readOnlyPredicate);
+ }
+
+ public SentineledConnectionProvider(String masterName, JedisClientConfig clientConfig, Cache cache, GenericObjectPoolConfig poolConfig, Set sentinels, JedisClientConfig sentinelClientConfig, ReadFrom readFrom, ReadOnlyCommands.ReadOnlyPredicate readOnlyPredicate) {
+ this(masterName, clientConfig, cache, poolConfig, sentinels, sentinelClientConfig,
+ DEFAULT_SUBSCRIBE_RETRY_WAIT_TIME_MILLIS, readFrom, readOnlyPredicate);
+ }
+
@Experimental
public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,
Cache clientSideCache, final GenericObjectPoolConfig poolConfig,
Set sentinels, final JedisClientConfig sentinelClientConfig) {
this(masterName, masterClientConfig, clientSideCache, poolConfig, sentinels, sentinelClientConfig,
- DEFAULT_SUBSCRIBE_RETRY_WAIT_TIME_MILLIS);
+ DEFAULT_SUBSCRIBE_RETRY_WAIT_TIME_MILLIS, ReadFrom.UPSTREAM, ReadOnlyCommands.asPredicate());
}
public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,
final GenericObjectPoolConfig poolConfig,
Set sentinels, final JedisClientConfig sentinelClientConfig,
final long subscribeRetryWaitTimeMillis) {
- this(masterName, masterClientConfig, null, poolConfig, sentinels, sentinelClientConfig, subscribeRetryWaitTimeMillis);
+ this(masterName, masterClientConfig, null, poolConfig, sentinels, sentinelClientConfig, subscribeRetryWaitTimeMillis, ReadFrom.UPSTREAM, ReadOnlyCommands.asPredicate());
}
@Experimental
public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig,
Cache clientSideCache, final GenericObjectPoolConfig poolConfig,
Set sentinels, final JedisClientConfig sentinelClientConfig,
- final long subscribeRetryWaitTimeMillis) {
+ final long subscribeRetryWaitTimeMillis, ReadFrom readFrom, ReadOnlyCommands.ReadOnlyPredicate readOnlyPredicate) {
this.masterName = masterName;
this.masterClientConfig = masterClientConfig;
@@ -100,11 +141,49 @@ public SentineledConnectionProvider(String masterName, final JedisClientConfig m
this.sentinelClientConfig = sentinelClientConfig;
this.subscribeRetryWaitTimeMillis = subscribeRetryWaitTimeMillis;
+ this.readFrom = readFrom;
+ this.READ_ONLY_COMMANDS = readOnlyPredicate;
HostAndPort master = initSentinels(sentinels);
initMaster(master);
}
+ private Connection getSlaveResource() {
+ int startIdx;
+ slavePoolsLock.lock();
+ try {
+ poolIndex++;
+ if (poolIndex >= slavePools.size()) {
+ poolIndex = 0;
+ }
+ startIdx = poolIndex;
+ } finally {
+ slavePoolsLock.unlock();
+ }
+ return _getSlaveResource(startIdx, 0);
+ }
+
+ private Connection _getSlaveResource(int idx, int cnt) {
+ PoolInfo poolInfo;
+ slavePoolsLock.lock();
+ try {
+ if (cnt >= slavePools.size()) {
+ return null;
+ }
+ poolInfo = slavePools.get(idx % slavePools.size());
+ } finally {
+ slavePoolsLock.unlock();
+ }
+
+ try {
+ Connection jedis = poolInfo.pool.getResource();
+ return jedis;
+ } catch (Exception e) {
+ LOG.error("get connection fail:", e);
+ return _getSlaveResource(idx + 1, cnt + 1);
+ }
+ }
+
@Override
public Connection getConnection() {
return pool.getResource();
@@ -112,7 +191,43 @@ public Connection getConnection() {
@Override
public Connection getConnection(CommandArguments args) {
- return pool.getResource();
+ boolean isReadCommand = READ_ONLY_COMMANDS.isReadOnly(args);
+ if (!isReadCommand) {
+ return pool.getResource();
+ }
+
+ Connection conn;
+ switch (readFrom) {
+ case REPLICA:
+ conn = getSlaveResource();
+ if (conn == null) {
+ throw new JedisException("all replica is invalid");
+ }
+ return conn;
+ case UPSTREAM_PREFERRED:
+ try {
+ conn = pool.getResource();
+ if (conn != null) {
+ return conn;
+ }
+ } catch (Exception e) {
+ LOG.error("get master connection error", e);
+ }
+
+ conn = getSlaveResource();
+ if (conn == null) {
+ throw new JedisException("all redis instance is invalid");
+ }
+ return conn;
+ case REPLICA_PREFERRED:
+ conn = getSlaveResource();
+ if (conn != null) {
+ return conn;
+ }
+ return pool.getResource();
+ default:
+ return pool.getResource();
+ }
}
@Override
@@ -130,6 +245,10 @@ public void close() {
sentinelListeners.forEach(SentinelListener::shutdown);
pool.close();
+
+ for (PoolInfo slavePool : slavePools) {
+ slavePool.pool.close();
+ }
}
public HostAndPort getCurrentMaster() {
@@ -180,6 +299,88 @@ private ConnectionPool createNodePool(HostAndPort master) {
}
}
+ private void initSlaves(List slaves) {
+ List removedSlavePools = new ArrayList<>();
+ slavePoolsLock.lock();
+ try {
+ for (int i = slavePools.size()-1; i >= 0; i--) {
+ PoolInfo poolInfo = slavePools.get(i);
+ boolean found = false;
+ for (HostAndPort slave : slaves) {
+ String host = slave.toString();
+ if (poolInfo.host.equals(host)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ removedSlavePools.add(slavePools.remove(i));
+ }
+ }
+
+ for (HostAndPort slave : slaves) {
+ addSlave(slave);
+ }
+ } finally {
+ slavePoolsLock.unlock();
+ if (!removedSlavePools.isEmpty() && clientSideCache != null) {
+ clientSideCache.flush();
+ }
+
+ for (PoolInfo removedSlavePool : removedSlavePools) {
+ removedSlavePool.pool.destroy();
+ }
+ }
+ }
+
+ private static boolean isHealthy(String flags) {
+ for (String flag : flags.split(",")) {
+ switch (flag.trim()) {
+ case "s_down":
+ case "o_down":
+ case "disconnected":
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void addSlave(HostAndPort slave) {
+ String newSlaveHost = slave.toString();
+ slavePoolsLock.lock();
+ try {
+ for (int i = 0; i < this.slavePools.size(); i++) {
+ PoolInfo poolInfo = this.slavePools.get(i);
+ if (poolInfo.host.equals(newSlaveHost)) {
+ return;
+ }
+ }
+ slavePools.add(new PoolInfo(newSlaveHost, createNodePool(slave)));
+ } finally {
+ slavePoolsLock.unlock();
+ }
+ }
+
+ private void removeSlave(HostAndPort slave) {
+ String newSlaveHost = slave.toString();
+ PoolInfo removed = null;
+ slavePoolsLock.lock();
+ try {
+ for (int i = 0; i < this.slavePools.size(); i++) {
+ PoolInfo poolInfo = this.slavePools.get(i);
+ if (poolInfo.host.equals(newSlaveHost)) {
+ removed = slavePools.remove(i);
+ break;
+ }
+ }
+ } finally {
+ slavePoolsLock.unlock();
+ }
+ if (removed != null) {
+ removed.pool.destroy();
+ }
+ }
+
private HostAndPort initSentinels(Set sentinels) {
HostAndPort master = null;
@@ -275,6 +476,24 @@ public void run() {
sentinelJedis = new Jedis(node, sentinelClientConfig);
+ List