├── .gitignore ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── mkfree │ └── sentinel │ ├── RedisInfo.java │ ├── RedisSentinel.java │ ├── RedisSentinelJedisPool.java │ └── RedisSentinelShardedJedisPool.java └── test └── java └── com └── mkfree └── sentinel └── RedisSentinelClientTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | classes/ 2 | lib/ 3 | .git/ 4 | .settings/ 5 | .project 6 | .classpath 7 | target/ 8 | cache/ 9 | lucene/ 10 | u/ 11 | logs/ 12 | kblog/ 13 | bin/ 14 | config.product -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | redis-sentinel-java 2 | ======== 3 | 4 | 本项目主要是做一个redis-sentinel java客户端集群的自动切换方案,主要为了学习而玩。我相信有非常多的bug,请原谅。 5 | 6 | - 简单使用 7 | - 环境要求 8 | - redis sentinel的集群redis环境 9 | 10 | ``` 11 | git clone git@github.com:oyhk/redis-sentinel-java.git 12 | ``` 13 | 14 | ``` 15 | public static void main(String[] args) throws InterruptedException { 16 | String host = "192.168.9.17"; 17 | int port = 26379; 18 | String clusterName = "master1"; 19 | RedisSentinel redisSentinelJedisPool = new RedisSentinelJedisPool(host, port, clusterName); 20 | 21 | Jedis jedis = null; 22 | try { 23 | jedis = (Jedis) redisSentinelJedisPool.getResource(); 24 | jedis.set("key", "value"); 25 | } catch (Exception e) { 26 | e.printStackTrace(); 27 | } finally { 28 | client.returnBrokenResource(jedis); 29 | } 30 | } 31 | 32 | ``` 33 | 34 | 35 | 36 | 自动切换日志信息 37 | 38 | ``` 39 | {info-refresh=9917, port=6379, quorum=1, num-slaves=1, flags=master, last-ok-ping-reply=392, pending-commands=0, num-other-sentinels=0, name=master1, last-ping-reply=392, runid=0dec551778d0ce9857478e82521a3b7ed2984070, ip=192.168.9.19} 40 | [192.168.9.19, 6379] 41 | set1 42 | set2 43 | set3 44 | set4 45 | [192.168.9.19, 6379] 46 | set5 47 | redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: 拒绝连接 48 | at redis.clients.jedis.Connection.connect(Connection.java:137) 49 | at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:65) 50 | at redis.clients.jedis.Connection.sendCommand(Connection.java:82) 51 | at redis.clients.jedis.BinaryClient.set(BinaryClient.java:82) 52 | at redis.clients.jedis.Client.set(Client.java:23) 53 | at redis.clients.jedis.Jedis.set(Jedis.java:43) 54 | at redis.clients.jedis.ShardedJedis.set(ShardedJedis.java:31) 55 | at com.mkfree.sentinel.RedisSentinelClientTest$1.run(RedisSentinelClientTest.java:26) 56 | at java.lang.Thread.run(Thread.java:722) 57 | Caused by: java.net.ConnectException: 拒绝连接 58 | at java.net.PlainSocketImpl.socketConnect(Native Method) 59 | at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339) 60 | at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200) 61 | at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182) 62 | at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:391) 63 | at java.net.Socket.connect(Socket.java:579) 64 | at redis.clients.jedis.Connection.connect(Connection.java:132) 65 | ... 8 more 66 | [192.168.9.19, 6379] 67 | [192.168.9.19, 6379] 68 | [192.168.9.19, 6379] 69 | [192.168.9.18, 6379] 70 | 主redis发生故障,自动切换... 71 | [192.168.9.18, 6379] 72 | [192.168.9.18, 6379] 73 | set1 74 | set2 75 | set3 76 | [192.168.9.18, 6379] 77 | set4 78 | ``` -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.mkfree 5 | redis-sentinel-java 6 | 0.0.1-SNAPSHOT 7 | 8 | 2.2.1 9 | 4.10 10 | 11 | 12 | 13 | redis.clients 14 | jedis 15 | ${redis-client.version} 16 | 17 | 18 | junit 19 | junit 20 | ${junit.version} 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/java/com/mkfree/sentinel/RedisInfo.java: -------------------------------------------------------------------------------- 1 | package com.mkfree.sentinel; 2 | 3 | /** 4 | * redis集群主从信息 5 | * 6 | * @author oyhk 7 | * 8 | * 2013-11-27 下午4:06:14 9 | */ 10 | public class RedisInfo { 11 | private String host; 12 | private int port; 13 | private String name; 14 | 15 | public String getHost() { 16 | return host; 17 | } 18 | 19 | public void setHost(String host) { 20 | this.host = host; 21 | } 22 | 23 | public int getPort() { 24 | return port; 25 | } 26 | 27 | public void setPort(int port) { 28 | this.port = port; 29 | } 30 | 31 | public String getName() { 32 | return name; 33 | } 34 | 35 | public void setName(String name) { 36 | this.name = name; 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/main/java/com/mkfree/sentinel/RedisSentinel.java: -------------------------------------------------------------------------------- 1 | package com.mkfree.sentinel; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import org.apache.commons.pool.impl.GenericObjectPool; 8 | 9 | import redis.clients.jedis.Jedis; 10 | import redis.clients.jedis.JedisPool; 11 | import redis.clients.jedis.JedisShardInfo; 12 | import redis.clients.jedis.ShardedJedis; 13 | import redis.clients.jedis.ShardedJedisPool; 14 | 15 | /** 16 | * redis 集群自动切换客户端 17 | * 18 | * @author oyhk 19 | * 20 | * 2013-12-3 上午11:12:08 21 | */ 22 | public abstract class RedisSentinel { 23 | 24 | // jedis 分片 共享连接池(目前使用静态变量解决) 25 | public ShardedJedisPool shardedJedisPool = null; 26 | // jedis 不分片 共享连接池(目前使用静态变量解决) 27 | public JedisPool jedisPool = null; 28 | // 连接池的配置 (如果不设置,使用默认值) 29 | protected GenericObjectPool.Config poolConfig = new GenericObjectPool.Config(); 30 | // jedisSentinel 监控实例 31 | protected Jedis jedisSentinel = null; 32 | 33 | // 常用的静态变量 34 | public static final String IP = "ip"; 35 | public static final String PORT = "port"; 36 | public static final String NAME = "name"; 37 | 38 | private String nextMaster = null; 39 | private String upMaster = null; 40 | 41 | /** 42 | * 创建多个集群共享的连接池 43 | */ 44 | protected void createJedisPool(String... clusterName) { 45 | List redisInfo = jedisSentinel.sentinelGetMasterAddrByName(clusterName[0]); 46 | String host = redisInfo.get(0); 47 | int port = Integer.parseInt(redisInfo.get(1)); 48 | jedisPool = new JedisPool(poolConfig, host, port); 49 | } 50 | 51 | /** 52 | * 检查redis sentinel 主/从redis服务是否正常, 当发生故障时,当redis sentinel监控自动切换 从redis 升级为主redis,需要重新初始化jedispool 53 | * 54 | * @param redisSentinel 那种监控的连接池(是否分片) 55 | * @param againCheckTime 单位(毫秒/millisecond) 56 | * @param clusterName 集群名 57 | */ 58 | protected void checkRedisSentinelServer(final RedisSentinel redisSentinel, final int againCheckTime, final String... clusterName) { 59 | System.out.println("检查redis sentinel 主/从redis服务是否正常,任务开始..."); 60 | Runnable checkRedisSentinelRunnable = new Runnable() { 61 | @Override 62 | public void run() { 63 | try { 64 | while (true) { 65 | jedisSentinel.ping(); 66 | List masters = jedisSentinel.sentinelGetMasterAddrByName(clusterName[0]); 67 | String master = masters.toString(); 68 | if (upMaster == null || upMaster.equals("")) { 69 | upMaster = master; 70 | } 71 | if (nextMaster == null || nextMaster.equals("") || !nextMaster.equals(master)) { 72 | nextMaster = master; 73 | } 74 | if (nextMaster.equals(upMaster)) { 75 | continue; 76 | } 77 | System.out.println("主redis发生故障,自动切换..."); 78 | if (redisSentinel instanceof RedisSentinelJedisPool) { 79 | createJedisPool(clusterName);// 重新初始化jedispool 80 | System.out.println("重新初始化jedispool..."); 81 | } else if (redisSentinel instanceof RedisSentinelShardedJedisPool) { 82 | // 暂时不考虑分片 83 | } 84 | upMaster = nextMaster; 85 | Thread.sleep(againCheckTime); 86 | } 87 | } catch (Exception e) { 88 | e.printStackTrace(); 89 | System.out.println("redis sentinel 监控异常,请检查..."); 90 | } 91 | } 92 | }; 93 | new Thread(checkRedisSentinelRunnable).start(); 94 | } 95 | 96 | /** 97 | * 获取客户端连接池 98 | * 99 | * @return 100 | */ 101 | public abstract Object getResource(); 102 | 103 | /** 104 | * 返回一个客户端连接到连接池 105 | * 106 | * @param resource 107 | */ 108 | public abstract void returnBrokenResource(ShardedJedis resource); 109 | 110 | /** 111 | * 返回一个客户端连接到连接池 112 | * 113 | * @param resource 114 | */ 115 | public abstract void returnBrokenResource(Jedis resource); 116 | 117 | public GenericObjectPool.Config getPoolConfig() { 118 | return poolConfig; 119 | } 120 | 121 | public void setPoolConfig(GenericObjectPool.Config poolConfig) { 122 | this.poolConfig = poolConfig; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/mkfree/sentinel/RedisSentinelJedisPool.java: -------------------------------------------------------------------------------- 1 | package com.mkfree.sentinel; 2 | 3 | import redis.clients.jedis.Jedis; 4 | import redis.clients.jedis.ShardedJedis; 5 | 6 | public class RedisSentinelJedisPool extends RedisSentinel { 7 | 8 | /** 9 | * 创建redis集群客户端 10 | * 11 | * @param host ip地址 12 | * @param port 端口 13 | * @param clusterName 群集名 14 | */ 15 | public RedisSentinelJedisPool(String host, int port, String... clusterName) { 16 | this.jedisSentinel = new Jedis(host, port); 17 | this.createJedisPool(clusterName); 18 | this.checkRedisSentinelServer(this, 5000, clusterName); 19 | } 20 | 21 | @Override 22 | public Object getResource() { 23 | return jedisPool.getResource(); 24 | } 25 | 26 | @Override 27 | public void returnBrokenResource(ShardedJedis resource) { 28 | 29 | } 30 | 31 | @Override 32 | public void returnBrokenResource(Jedis resource) { 33 | jedisPool.returnBrokenResource(resource); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/mkfree/sentinel/RedisSentinelShardedJedisPool.java: -------------------------------------------------------------------------------- 1 | package com.mkfree.sentinel; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import org.apache.commons.pool.impl.GenericObjectPool; 8 | 9 | import redis.clients.jedis.Jedis; 10 | import redis.clients.jedis.JedisShardInfo; 11 | import redis.clients.jedis.ShardedJedis; 12 | import redis.clients.jedis.ShardedJedisPool; 13 | 14 | /** 15 | * 创建redis集群客户端 (这是一个集群并分片的连接池) 16 | * 17 | * @author oyhk 18 | * 19 | * 2013-12-3 下午6:00:25 20 | */ 21 | public class RedisSentinelShardedJedisPool extends RedisSentinel { 22 | 23 | /** 24 | * 创建redis集群客户端 25 | * 26 | * @param host ip地址 27 | * @param port 端口 28 | * @param clusterName 群集名 29 | */ 30 | public RedisSentinelShardedJedisPool(String host, int port, String clusterName) { 31 | this.jedisSentinel = new Jedis(host, port); 32 | this.createShardedJedisPool(clusterName); 33 | this.checkRedisSentinelServer(this, 5000, clusterName); 34 | } 35 | 36 | /** 37 | * 创建多个集群共享的连接池 38 | */ 39 | private void createShardedJedisPool(String clusterName) { 40 | List redisInfos = new ArrayList(); 41 | List> lists = jedisSentinel.sentinelMasters(); 42 | for (int i = 0; i < lists.size(); i++) { 43 | Map map = lists.get(i); 44 | System.out.println(map); 45 | RedisInfo redisInfo = new RedisInfo(); 46 | redisInfo.setHost(map.get(IP)); 47 | redisInfo.setPort(Integer.parseInt(map.get(PORT))); 48 | redisInfo.setName(map.get(NAME)); 49 | redisInfos.add(redisInfo); 50 | } 51 | List shards = new ArrayList(); 52 | for (int i = 0; i < redisInfos.size(); i++) { 53 | RedisInfo redisInfo = redisInfos.get(i); 54 | shards.add(new JedisShardInfo(redisInfo.getHost(), redisInfo.getPort(), redisInfo.getName())); 55 | } 56 | shardedJedisPool = new ShardedJedisPool(poolConfig, shards); 57 | } 58 | 59 | /** 60 | * 返回redis 客户端资源 61 | * 62 | * @return 63 | */ 64 | public ShardedJedis getResource() { 65 | return shardedJedisPool.getResource(); 66 | } 67 | 68 | /** 69 | * 把资源返回连接池 70 | * 71 | * @param shardedJedis 72 | */ 73 | @Override 74 | public void returnBrokenResource(ShardedJedis shardedJedis) { 75 | shardedJedisPool.returnBrokenResource(shardedJedis); 76 | } 77 | 78 | @Override 79 | public void returnBrokenResource(Jedis resource) { 80 | // TODO Auto-generated method stub 81 | } 82 | 83 | public ShardedJedisPool getShardedJedisPool() { 84 | return shardedJedisPool; 85 | } 86 | 87 | public Jedis getJedisSentinel() { 88 | return jedisSentinel; 89 | } 90 | 91 | public void setJedisSentinel(Jedis jedisSentinel) { 92 | this.jedisSentinel = jedisSentinel; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/com/mkfree/sentinel/RedisSentinelClientTest.java: -------------------------------------------------------------------------------- 1 | package com.mkfree.sentinel; 2 | 3 | import redis.clients.jedis.Jedis; 4 | import redis.clients.jedis.ShardedJedis; 5 | 6 | public class RedisSentinelClientTest { 7 | 8 | public static void main(String[] args) throws InterruptedException { 9 | String host = "192.168.9.17"; 10 | int port = 26379; 11 | String clusterName = "master1"; 12 | // RedisSentinel redisSentinelShardedJedisPool = new RedisSentinelShardedJedisPool(host, port, clusterName); 13 | // testSet(redisSentinelShardedJedisPool); 14 | RedisSentinel redisSentinelJedisPool = new RedisSentinelJedisPool(host, port, clusterName); 15 | setRedisSentinelJedisPool(redisSentinelJedisPool); 16 | } 17 | 18 | /** 19 | * 模拟多线程操作 20 | */ 21 | public static void setRedisSentinelShardedJedisPool(final RedisSentinel client) { 22 | new Thread(new Runnable() { 23 | @Override 24 | public void run() { 25 | try { 26 | int count = 0; 27 | while (true) { 28 | ShardedJedis shardedJedis = (ShardedJedis) client.getResource(); 29 | Thread.sleep(1000); 30 | shardedJedis.set("b" + count, "b" + count); 31 | client.returnBrokenResource(shardedJedis); 32 | count++; 33 | System.out.println("set" + count); 34 | } 35 | } catch (Exception e) { 36 | e.printStackTrace(); 37 | try { 38 | Thread.sleep(30000); 39 | } catch (InterruptedException e1) { 40 | e1.printStackTrace(); 41 | } 42 | } 43 | } 44 | }).start(); 45 | } 46 | 47 | /** 48 | * 模拟多线程操作 49 | */ 50 | public static void setRedisSentinelJedisPool(final RedisSentinel client) { 51 | new Thread(new Runnable() { 52 | @Override 53 | public void run() { 54 | int count = 0; 55 | while (true) { 56 | Jedis jedis = null; 57 | try { 58 | jedis = (Jedis) client.getResource(); 59 | Thread.sleep(1000); 60 | jedis.set("b" + count, "b" + count); 61 | count++; 62 | System.out.println("set" + count); 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | try { 66 | Thread.sleep(30000); 67 | } catch (InterruptedException e1) { 68 | e1.printStackTrace(); 69 | } 70 | } finally { 71 | client.returnBrokenResource(jedis); 72 | } 73 | } 74 | } 75 | }).start(); 76 | } 77 | } 78 | --------------------------------------------------------------------------------