Sentinel集羣的搭建和測試
master: 127.0.0.16379
slave1: 127.0.0.1 6380
slave2: 127.0.0.1 6381
master-sentinel: 127.0.0.1 26379
slave1-sentinel: 127.0.0.1 26380
slave2-sentinel: 127.0.0.1 26381
1.2 集羣配置
1) Master
redis.conf:
daemonize yes
port 6379
requirepass pw
masterauth pw
appendonly no
save ""
slave-read-only yes
6379-sentinal.conf
port 26379
sentinel monitor mymaster 192.168.0.105 6379 2
sentinel auth-pass mymaster foobared
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 900000
2) Salve1
Redis.conf
daemonize yes
port 6380
requirepass pw
masterauth pw
appendonly no
save ""
slave-read-only yes
slaveof 192.168.0.105 6379
6380-sentinal.conf
port 26380
sentinel monitormymaster 192.168.0.105 6379 2
sentinel auth-passmymaster pw
sentineldown-after-milliseconds mymaster 30000
sentinel parallel-syncsmymaster 1
sentinel failover-timeout mymaster 900000
3) Salve2
Redis.conf
daemonize yes
port 6381
requirepass pw
masterauth pw
appendonly no
save ""
slave-read-only yes
slaveof 192.168.0.105 6379
6381-sentinal.conf
port 26381
sentinel monitormymaster 192.168.0.105 6379 2
sentinel auth-passmymaster pw
sentineldown-after-milliseconds mymaster 30000
sentinel parallel-syncsmymaster 1
sentinel failover-timeout mymaster 900000
1.3 啓動集羣
寫一個批量啓動集羣的shell腳本:
St-batch-start.sh
./bin/redis-server./st_cluster_1/master-6379/redis.conf&
./bin/redis-sentinel./st_cluster_1/master-6379/6379-sentinal.conf&
./bin/redis-server./st_cluster_1/salve-6380/redis.conf&
./bin/redis-sentinel ./st_cluster_1/salve-6380/6380-sentinal.conf&
./bin/redis-server./st_cluster_1/salve-6381/redis.conf&
./bin/redis-sentinel./st_cluster_1/salve-6381/6381-sentinal.conf&
#sh st-batch-start.sh
寫一個批量關閉集羣的shell腳本:
St-batch-stop.sh
kill -9 `ps -ef | grep redis | awk '{print $2}'`
#sh st-batch-stop.sh
1.1 啓動後的結果
起一個會話查看master的情況:
redis-cli -h 192.168.0.105 -p 6379
auth pw
info Replication
起一個會話查詢salve1的情況:
redis-cli -h192.168.0.105 -p 6380
起一個會話查詢salve2的情況:
redis-cli -h192.168.0.105 -p 6381
此時查看sentinal各個主機的配置文件:
端口爲26379的sentinel的配置文件沒有改變:
端口爲26380的sentinel的配置文件有改變:
端口爲26381的文件被重寫了,因爲它們發現了master的salve 6381,和另一個sentinel 26381。
1.4 場景測試
----場景1:master宕機
master-sentinel作爲master 1的leader,會選取一個master 1的slave作爲新的master。slave的選取是根據一個判斷DNS情況的優先級來得到,優先級相同通過runid的排序得到,但目前優先級設定還沒實現,所以直接獲取runid排序得到slave 1。然後發送命令slaveofno one來取消slave 1的slave狀態來轉換爲master。當其他sentinel觀察到該slave成爲master後,就知道錯誤處理例程啓動了。sentinel A然後發送給其他slave slaveof new-slave-ip-port 命令,當所有slave都配置完後,sentinelA從監測的masters列表中刪除故障master,然後通知其他sentinels。
關閉master:
現在6379是master,6380和6381是salve。
Sentinel底下觀察選舉新的master的過程:
顯示了failover的過程:
選擇6380爲master:
----場景2:master恢復
重新啓動原來的master:
./bin/redis-server./st_cluster_1/master-6379/redis.conf&
查看sentinel狀態:
原來的master自動切換成slave,不會自動恢復成master。
----場景3:salve1宕機
接着上面的繼續實驗。
關閉slave1:6379:
查看master的Replication信息:
此時只存在一個slave。
----場景4:salve1重啓
./bin/redis-server./st_cluster_1/master-6379/redis.conf&
查看sentinel狀態:
sentinel能快速的發現slave加入到集羣中:
查看master的Replication信息:
1.5 Java客戶端測試
package redis.clients.jedis.tests;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.tests.utils.JedisSentinelTestUtil;
public class JedisSentinelPoolTest extends JedisTestBase {
private static final String MASTER_NAME = "mymaster";
private static final String localHost = "192.168.0.106";
protected static HostAndPort master = new HostAndPort(localHost, 6379);
protected static HostAndPort slave1 = new HostAndPort(localHost, 6380);
protected static HostAndPort slave2 = new HostAndPort(localHost, 6381);
protected static HostAndPort sentinel1 = new HostAndPort(localHost, 26379);
protected static HostAndPort sentinel2 = new HostAndPort(localHost, 26380);
protected static HostAndPort sentinel3 = new HostAndPort(localHost, 26381);
protected static Jedis sentinelJedis1;
protected static Jedis sentinelJedis2;
protected static Jedis sentinelJedis3;
protected Set<String> sentinels = new HashSet<String>();
@Before
public void setUp() throws Exception {
sentinels.add(sentinel1.toString());
sentinels.add(sentinel2.toString());
sentinels.add(sentinel3.toString());
sentinelJedis1 = new Jedis(sentinel1.getHost(), sentinel1.getPort());
sentinelJedis2 = new Jedis(sentinel2.getHost(), sentinel2.getPort());
sentinelJedis3 = new Jedis(sentinel3.getHost(), sentinel3.getPort());
}
@Test(expected = JedisConnectionException.class)
public void initializeWithNotAvailableSentinelsShouldThrowException() {
Set<String> wrongSentinels = new HashSet<String>();
wrongSentinels.add(new HostAndPort(localHost, 65432).toString());
wrongSentinels.add(new HostAndPort(localHost, 65431).toString());
JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, wrongSentinels);
pool.destroy();
}
@Test(expected = JedisException.class)
public void initializeWithNotMonitoredMasterNameShouldThrowException() {
final String wrongMasterName = "wrongMasterName";
JedisSentinelPool pool = new JedisSentinelPool(wrongMasterName, sentinels);
pool.destroy();
}
@Test
public void checkCloseableConnections() throws Exception {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, config, 10000,
"pw", 3);
Jedis jedis = pool.getResource();
jedis.auth("pw");
jedis.set("foo", "bar");
String ss = jedis.get("test");
assertEquals("bar", jedis.get("foo"));
jedis.close();
pool.close();
assertTrue(pool.isClosed());
}
@Test
public void ensureSafeTwiceFailover() throws InterruptedException {
JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels,
new GenericObjectPoolConfig(), 9000, "pw", 3);
forceFailover(pool);
// after failover sentinel needs a bit of time to stabilize before a new
// failover
Thread.sleep(100);
forceFailover(pool);
// you can test failover as much as possible
}
@Test
public void returnResourceShouldResetState() {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(1);
config.setBlockWhenExhausted(false);
JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, config, 1000,
"pw", 3);
Jedis jedis = pool.getResource();
Jedis jedis2 = null;
try {
jedis.set("hello", "jedis");
Transaction t = jedis.multi();
t.set("hello", "world");
jedis.close();
jedis2 = pool.getResource();
assertTrue(jedis == jedis2);
assertEquals("jedis", jedis2.get("hello"));
} catch (JedisConnectionException e) {
if (jedis2 != null) {
jedis2 = null;
}
} finally {
jedis2.close();
pool.destroy();
}
}
@Test
public void checkResourceIsCloseable() {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(1);
config.setBlockWhenExhausted(false);
JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, config, 1000,
"pw", 3);
Jedis jedis = pool.getResource();
try {
jedis.set("hello", "jedis");
} finally {
jedis.close();
}
Jedis jedis2 = pool.getResource();
try {
assertEquals(jedis, jedis2);
} finally {
jedis2.close();
}
}
private void forceFailover(JedisSentinelPool pool) throws InterruptedException {
HostAndPort oldMaster = pool.getCurrentHostMaster();
// jedis connection should be master
Jedis beforeFailoverJedis = pool.getResource();
assertEquals("PONG", beforeFailoverJedis.ping());
waitForFailover(pool, oldMaster);
Jedis afterFailoverJedis = pool.getResource();
assertEquals("PONG", afterFailoverJedis.ping());
assertEquals("pw", afterFailoverJedis.configGet("requirepass").get(1));
assertEquals(3, afterFailoverJedis.getDB());
// returning both connections to the pool should not throw
beforeFailoverJedis.close();
afterFailoverJedis.close();
}
private void waitForFailover(JedisSentinelPool pool, HostAndPort oldMaster)
throws InterruptedException {
HostAndPort newMaster = JedisSentinelTestUtil.waitForNewPromotedMaster(MASTER_NAME,
sentinelJedis1, sentinelJedis2);
waitForJedisSentinelPoolRecognizeNewMaster(pool, newMaster);
}
private void waitForJedisSentinelPoolRecognizeNewMaster(JedisSentinelPool pool,
HostAndPort newMaster) throws InterruptedException {
while (true) {
HostAndPort currentHostMaster = pool.getCurrentHostMaster();
if (newMaster.equals(currentHostMaster)) break;
System.out.println("JedisSentinelPool's master is not yet changed, sleep...");
Thread.sleep(100);
}
}
}