Merge branch 'master' of https://github.com/nrodrigues/jedis into nrodrigues-master

This commit is contained in:
Jungtaek Lim
2014-09-11 10:52:29 +09:00
7 changed files with 97 additions and 37 deletions

View File

@@ -116,6 +116,18 @@ pidfile /tmp/sentinel3.pid
logfile /tmp/sentinel3.log logfile /tmp/sentinel3.log
endef endef
define REDIS_SENTINEL4
port 26382
daemonize yes
sentinel monitor mymaster 127.0.0.1 6381 1
sentinel auth-pass mymaster foobared
sentinel down-after-milliseconds mymaster 2000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 120000
pidfile /tmp/sentinel4.pid
logfile /tmp/sentinel4.log
endef
# CLUSTER REDIS NODES # CLUSTER REDIS NODES
define REDIS_CLUSTER_NODE1_CONF define REDIS_CLUSTER_NODE1_CONF
daemonize yes daemonize yes
@@ -199,6 +211,7 @@ export REDIS7_CONF
export REDIS_SENTINEL1 export REDIS_SENTINEL1
export REDIS_SENTINEL2 export REDIS_SENTINEL2
export REDIS_SENTINEL3 export REDIS_SENTINEL3
export REDIS_SENTINEL4
export REDIS_CLUSTER_NODE1_CONF export REDIS_CLUSTER_NODE1_CONF
export REDIS_CLUSTER_NODE2_CONF export REDIS_CLUSTER_NODE2_CONF
export REDIS_CLUSTER_NODE3_CONF export REDIS_CLUSTER_NODE3_CONF
@@ -219,6 +232,8 @@ start: cleanup
echo "$$REDIS_SENTINEL2" > /tmp/sentinel2.conf && redis-server /tmp/sentinel2.conf --sentinel echo "$$REDIS_SENTINEL2" > /tmp/sentinel2.conf && redis-server /tmp/sentinel2.conf --sentinel
@sleep 0.5 @sleep 0.5
echo "$$REDIS_SENTINEL3" > /tmp/sentinel3.conf && redis-server /tmp/sentinel3.conf --sentinel echo "$$REDIS_SENTINEL3" > /tmp/sentinel3.conf && redis-server /tmp/sentinel3.conf --sentinel
@sleep 0.5
echo "$$REDIS_SENTINEL4" > /tmp/sentinel4.conf && redis-server /tmp/sentinel4.conf --sentinel
echo "$$REDIS_CLUSTER_NODE1_CONF" | redis-server - echo "$$REDIS_CLUSTER_NODE1_CONF" | redis-server -
echo "$$REDIS_CLUSTER_NODE2_CONF" | redis-server - echo "$$REDIS_CLUSTER_NODE2_CONF" | redis-server -
echo "$$REDIS_CLUSTER_NODE3_CONF" | redis-server - echo "$$REDIS_CLUSTER_NODE3_CONF" | redis-server -
@@ -241,6 +256,7 @@ stop:
kill `cat /tmp/sentinel1.pid` kill `cat /tmp/sentinel1.pid`
kill `cat /tmp/sentinel2.pid` kill `cat /tmp/sentinel2.pid`
kill `cat /tmp/sentinel3.pid` kill `cat /tmp/sentinel3.pid`
kill `cat /tmp/sentinel4.pid`
kill `cat /tmp/redis_cluster_node1.pid` || true kill `cat /tmp/redis_cluster_node1.pid` || true
kill `cat /tmp/redis_cluster_node2.pid` || true kill `cat /tmp/redis_cluster_node2.pid` || true
kill `cat /tmp/redis_cluster_node3.pid` || true kill `cat /tmp/redis_cluster_node3.pid` || true

View File

@@ -1,5 +1,7 @@
package redis.clients.jedis; package redis.clients.jedis;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject; import org.apache.commons.pool2.impl.DefaultPooledObject;
@@ -8,8 +10,7 @@ import org.apache.commons.pool2.impl.DefaultPooledObject;
* PoolableObjectFactory custom impl. * PoolableObjectFactory custom impl.
*/ */
class JedisFactory implements PooledObjectFactory<Jedis> { class JedisFactory implements PooledObjectFactory<Jedis> {
private final String host; private final AtomicReference<HostAndPort> hostAndPort = new AtomicReference<HostAndPort>();
private final int port;
private final int timeout; private final int timeout;
private final String password; private final String password;
private final int database; private final int database;
@@ -23,14 +24,17 @@ class JedisFactory implements PooledObjectFactory<Jedis> {
public JedisFactory(final String host, final int port, final int timeout, public JedisFactory(final String host, final int port, final int timeout,
final String password, final int database, final String clientName) { final String password, final int database, final String clientName) {
super(); super();
this.host = host; this.hostAndPort.set(new HostAndPort(host, port));
this.port = port;
this.timeout = timeout; this.timeout = timeout;
this.password = password; this.password = password;
this.database = database; this.database = database;
this.clientName = clientName; this.clientName = clientName;
} }
public void setHostAndPort(final HostAndPort hostAndPort) {
this.hostAndPort.set(hostAndPort);
}
@Override @Override
public void activateObject(PooledObject<Jedis> pooledJedis) public void activateObject(PooledObject<Jedis> pooledJedis)
throws Exception { throws Exception {
@@ -60,7 +64,8 @@ class JedisFactory implements PooledObjectFactory<Jedis> {
@Override @Override
public PooledObject<Jedis> makeObject() throws Exception { public PooledObject<Jedis> makeObject() throws Exception {
final Jedis jedis = new Jedis(this.host, this.port, this.timeout); final HostAndPort hostAndPort = this.hostAndPort.get();
final Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), this.timeout);
jedis.connect(); jedis.connect();
if (null != this.password) { if (null != this.password) {
@@ -86,7 +91,13 @@ class JedisFactory implements PooledObjectFactory<Jedis> {
public boolean validateObject(PooledObject<Jedis> pooledJedis) { public boolean validateObject(PooledObject<Jedis> pooledJedis) {
final BinaryJedis jedis = pooledJedis.getObject(); final BinaryJedis jedis = pooledJedis.getObject();
try { try {
return jedis.isConnected() && jedis.ping().equals("PONG"); HostAndPort hostAndPort = this.hostAndPort.get();
String connectionHost = jedis.getClient().getHost();
int connectionPort = jedis.getClient().getPort();
return hostAndPort.getHost().equals(connectionHost) && hostAndPort.getPort() == connectionPort &&
jedis.isConnected() && jedis.ping().equals("PONG");
} catch (final Exception e) { } catch (final Exception e) {
return false; return false;
} }

View File

@@ -65,6 +65,7 @@ public class JedisSentinelPool extends Pool<Jedis> {
public JedisSentinelPool(String masterName, Set<String> sentinels, public JedisSentinelPool(String masterName, Set<String> sentinels,
final GenericObjectPoolConfig poolConfig, int timeout, final GenericObjectPoolConfig poolConfig, int timeout,
final String password, final int database) { final String password, final int database) {
this.poolConfig = poolConfig; this.poolConfig = poolConfig;
this.timeout = timeout; this.timeout = timeout;
this.password = password; this.password = password;
@@ -74,6 +75,7 @@ public class JedisSentinelPool extends Pool<Jedis> {
initPool(master); initPool(master);
} }
private volatile JedisFactory factory;
private volatile HostAndPort currentHostMaster; private volatile HostAndPort currentHostMaster;
public void destroy() { public void destroy() {
@@ -91,10 +93,18 @@ public class JedisSentinelPool extends Pool<Jedis> {
private void initPool(HostAndPort master) { private void initPool(HostAndPort master) {
if (!master.equals(currentHostMaster)) { if (!master.equals(currentHostMaster)) {
currentHostMaster = master; currentHostMaster = master;
if (factory == null) {
factory = new JedisFactory(master.getHost(), master.getPort(),
timeout, password, database);
initPool(poolConfig, factory);
} else {
factory.setHostAndPort(currentHostMaster);
// although we clear the pool, we still have to check the returned object
// in getResource, this call only clears idle instances, not borrowed instances
internalPool.clear();
}
log.info("Created JedisPool to master at " + master); log.info("Created JedisPool to master at " + master);
initPool(poolConfig,
new JedisFactory(master.getHost(), master.getPort(),
timeout, password, database));
} }
} }
@@ -115,19 +125,23 @@ public class JedisSentinelPool extends Pool<Jedis> {
log.fine("Connecting to Sentinel " + hap); log.fine("Connecting to Sentinel " + hap);
Jedis jedis = null;
try { try {
Jedis jedis = new Jedis(hap.getHost(), hap.getPort()); jedis = new Jedis(hap.getHost(), hap.getPort());
if (master == null) { if (master == null) {
master = toHostAndPort(jedis master = toHostAndPort(jedis
.sentinelGetMasterAddrByName(masterName)); .sentinelGetMasterAddrByName(masterName));
log.fine("Found Redis master at " + master); log.fine("Found Redis master at " + master);
jedis.disconnect();
break outer; break outer;
} }
} catch (JedisConnectionException e) { } catch (JedisConnectionException e) {
log.warning("Cannot connect to sentinel running @ " + hap log.warning("Cannot connect to sentinel running @ " + hap
+ ". Trying next one."); + ". Trying next one.");
} finally {
if (jedis != null) {
jedis.close();
}
} }
} }
@@ -164,9 +178,22 @@ public class JedisSentinelPool extends Pool<Jedis> {
@Override @Override
public Jedis getResource() { public Jedis getResource() {
Jedis jedis = super.getResource(); while (true) {
jedis.setDataSource(this); Jedis jedis = super.getResource();
return jedis; jedis.setDataSource(this);
// get a reference because it can change concurrently
final HostAndPort master = currentHostMaster;
final HostAndPort connection = new HostAndPort(jedis.getClient().getHost(),
jedis.getClient().getPort());
if (master.equals(connection)) {
// connected to the correct master
return jedis;
} else {
returnBrokenResource(jedis);
}
}
} }
public void returnBrokenResource(final Jedis resource) { public void returnBrokenResource(final Jedis resource) {

View File

@@ -23,6 +23,7 @@ public class HostAndPortUtil {
sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT)); sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT));
sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT + 1)); sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT + 1));
sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT + 2)); sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT + 2));
sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT + 3));
clusterHostAndPortList.add(new HostAndPort("localhost", 7379)); clusterHostAndPortList.add(new HostAndPort("localhost", 7379));
clusterHostAndPortList.add(new HostAndPort("localhost", 7380)); clusterHostAndPortList.add(new HostAndPort("localhost", 7380));

View File

@@ -21,18 +21,24 @@ public class JedisSentinelPoolTest extends JedisTestBase {
.get(2); .get(2);
protected static HostAndPort slave1 = HostAndPortUtil.getRedisServers() protected static HostAndPort slave1 = HostAndPortUtil.getRedisServers()
.get(3); .get(3);
protected static HostAndPort sentinel1 = HostAndPortUtil protected static HostAndPort sentinel1 = HostAndPortUtil
.getSentinelServers().get(1); .getSentinelServers().get(1);
protected static HostAndPort sentinel2 = HostAndPortUtil
.getSentinelServers().get(3);
protected static Jedis sentinelJedis1; protected static Jedis sentinelJedis1;
protected static Jedis sentinelJedis2;
protected Set<String> sentinels = new HashSet<String>(); protected Set<String> sentinels = new HashSet<String>();
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
sentinels.add(sentinel1.toString()); sentinels.add(sentinel1.toString());
sentinels.add(sentinel2.toString());
sentinelJedis1 = new Jedis(sentinel1.getHost(), sentinel1.getPort()); sentinelJedis1 = new Jedis(sentinel1.getHost(), sentinel1.getPort());
sentinelJedis2 = new Jedis(sentinel2.getHost(), sentinel2.getPort());
} }
@Test @Test
@@ -41,6 +47,8 @@ public class JedisSentinelPoolTest extends JedisTestBase {
new GenericObjectPoolConfig(), 1000, "foobared", 2); new GenericObjectPoolConfig(), 1000, "foobared", 2);
forceFailover(pool); forceFailover(pool);
// after failover sentinel needs a bit of time to stabilize before a new failover
Thread.sleep(100);
forceFailover(pool); forceFailover(pool);
// you can test failover as much as possible // you can test failover as much as possible
@@ -134,29 +142,25 @@ public class JedisSentinelPoolTest extends JedisTestBase {
HostAndPort oldMaster = pool.getCurrentHostMaster(); HostAndPort oldMaster = pool.getCurrentHostMaster();
// jedis connection should be master // jedis connection should be master
Jedis jedis = pool.getResource(); Jedis beforeFailoverJedis = pool.getResource();
assertEquals("PONG", jedis.ping()); assertEquals("PONG", beforeFailoverJedis.ping());
// It can throw JedisDataException while there's no slave to promote
// There's nothing we can do, so we just pass Exception to make test
// fail fast
sentinelJedis1.sentinelFailover(MASTER_NAME);
waitForFailover(pool, oldMaster); waitForFailover(pool, oldMaster);
// JedisSentinelPool recognize master but may not changed internal pool
// yet
Thread.sleep(100);
jedis = pool.getResource(); Jedis afterFailoverJedis = pool.getResource();
assertEquals("PONG", jedis.ping()); assertEquals("PONG", afterFailoverJedis.ping());
assertEquals("foobared", jedis.configGet("requirepass").get(1)); assertEquals("foobared", afterFailoverJedis.configGet("requirepass").get(1));
assertEquals(2, jedis.getDB().intValue()); assertEquals(2, afterFailoverJedis.getDB().intValue());
// returning both connections to the pool should not throw
beforeFailoverJedis.close();
afterFailoverJedis.close();
} }
private void waitForFailover(JedisSentinelPool pool, HostAndPort oldMaster) private void waitForFailover(JedisSentinelPool pool, HostAndPort oldMaster)
throws InterruptedException { throws InterruptedException {
HostAndPort newMaster = JedisSentinelTestUtil HostAndPort newMaster = JedisSentinelTestUtil
.waitForNewPromotedMaster(sentinelJedis1); .waitForNewPromotedMaster(MASTER_NAME, sentinelJedis1, sentinelJedis2);
waitForJedisSentinelPoolRecognizeNewMaster(pool, newMaster); waitForJedisSentinelPoolRecognizeNewMaster(pool, newMaster);
} }
@@ -166,10 +170,9 @@ public class JedisSentinelPoolTest extends JedisTestBase {
throws InterruptedException { throws InterruptedException {
while (true) { while (true) {
String host = pool.getCurrentHostMaster().getHost(); HostAndPort currentHostMaster = pool.getCurrentHostMaster();
int port = pool.getCurrentHostMaster().getPort();
if (host.equals(newMaster.getHost()) && port == newMaster.getPort()) if (newMaster.equals(currentHostMaster))
break; break;
System.out System.out

View File

@@ -85,16 +85,16 @@ public class JedisSentinelTest extends JedisTestBase {
public void sentinelFailover() throws InterruptedException { public void sentinelFailover() throws InterruptedException {
Jedis j = new Jedis(sentinelForFailover.getHost(), Jedis j = new Jedis(sentinelForFailover.getHost(),
sentinelForFailover.getPort()); sentinelForFailover.getPort());
Jedis j2 = new Jedis(sentinelForFailover.getHost(),
sentinelForFailover.getPort());
try { try {
List<String> masterHostAndPort = j List<String> masterHostAndPort = j
.sentinelGetMasterAddrByName(FAILOVER_MASTER_NAME); .sentinelGetMasterAddrByName(FAILOVER_MASTER_NAME);
HostAndPort currentMaster = new HostAndPort(masterHostAndPort.get(0), HostAndPort currentMaster = new HostAndPort(masterHostAndPort.get(0),
Integer.parseInt(masterHostAndPort.get(1))); Integer.parseInt(masterHostAndPort.get(1)));
String result = j.sentinelFailover(FAILOVER_MASTER_NAME);
assertEquals("OK", result);
JedisSentinelTestUtil.waitForNewPromotedMaster(j); JedisSentinelTestUtil.waitForNewPromotedMaster(FAILOVER_MASTER_NAME, j, j2);
masterHostAndPort = j masterHostAndPort = j
.sentinelGetMasterAddrByName(FAILOVER_MASTER_NAME); .sentinelGetMasterAddrByName(FAILOVER_MASTER_NAME);

View File

@@ -8,7 +8,8 @@ import redis.clients.jedis.JedisPubSub;
import redis.clients.jedis.tests.utils.FailoverAbortedException; import redis.clients.jedis.tests.utils.FailoverAbortedException;
public class JedisSentinelTestUtil { public class JedisSentinelTestUtil {
public static HostAndPort waitForNewPromotedMaster(Jedis sentinelJedis) public static HostAndPort waitForNewPromotedMaster(final String masterName,
final Jedis sentinelJedis, final Jedis commandJedis)
throws InterruptedException { throws InterruptedException {
final AtomicReference<String> newmaster = new AtomicReference<String>( final AtomicReference<String> newmaster = new AtomicReference<String>(
@@ -47,6 +48,7 @@ public class JedisSentinelTestUtil {
@Override @Override
public void onPSubscribe(String pattern, int subscribedChannels) { public void onPSubscribe(String pattern, int subscribedChannels) {
commandJedis.sentinelFailover(masterName);
} }
}, "*"); }, "*");