Jedis 连接哨兵模式

由于 Redis 的主从复制有一个致命的问题,即当 Master 服务器宕掉时,不会自动切换 Master,需要人为干预。鉴于这个问题,Redis 提供了哨兵功能。哨兵是 Redis 集群架构中非常重要的一个组件,哨兵的出现主要是解决了主从复制出现故障时需要人为干预的问题,哨兵实现了自动化的故障恢复。

注意:如何搭建 Redis 哨兵模式,请参考“Redis 哨兵模式”或者“Redis 多哨兵模式”。

单哨兵模式

单哨兵模式指只有一个哨兵,该哨兵负责监听所有的主从节点。如果该哨兵出现问题,那么主从模式的自动化故障恢复将失效。下面使用 Jedis 通过哨兵连接到 Redis,实现读写,代码如下:

Set<String> sentinels = new HashSet<>();
sentinels.add("127.0.0.1:26379");
JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels);
// 永远返回的都是 Master 连接
Jedis jedis = pool.getResource();

// 获取 Master 的地址信息
HostAndPort hostAndPort = pool.getCurrentHostMaster();
System.out.println("master = " + hostAndPort.getHost() + ":" + hostAndPort.getPort());

// 写入数据
jedis.set("title", "www.hxstrive.com");

// 读取数据
String result = jedis.get("title");
System.out.println("result = " + result);

多哨兵模式

多哨兵模式指配置多个哨兵,每个哨兵均对主从节点进行监听。如果某一个哨兵出现了问题,那么其他哨兵还是能够实现主从模式的自动化故障恢复。下面使用 Jedis 通过哨兵连接到 Redis,实现读写,代码如下:

Set<String> sentinels = new HashSet<>();
sentinels.add("127.0.0.1:26379");
sentinels.add("127.0.0.1:26378");
sentinels.add("127.0.0.1:26377");
JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels);
// 永远返回的都是 Master 连接
Jedis jedis = pool.getResource();

// 获取 Master 的地址信息
HostAndPort hostAndPort = pool.getCurrentHostMaster();
System.out.println("master = " + hostAndPort.getHost() + ":" + hostAndPort.getPort());

// 写入数据
jedis.set("title", "www.hxstrive.com");

// 读取数据
String result = jedis.get("title");
System.out.println("result = " + result);

读写分离

有的读者可能会有疑问?Jedis 在哨兵模式下是不是会自动实现读写分离,遗憾的是 Jedis 并没有实现读写分离。我们看看 JedisSentinelPool 的 getResource() 方法实现就真相大白了,部分源码如下:

@Override
public Jedis getResource() {
 while (true) {
   Jedis jedis = super.getResource();
   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);
   }
 }
}

上面源码中,if (master.equals(connection)) {} 代码表明返回的所有 Jedis 连接均是 Master 的连接。这就没有办法了,需要我们自己去实现读写分离,下面将通过一段简单的代码来实现读写分离,该代码仅仅用于学习,代码如下:

public class SentinelTest3 {

   class MyJedis {
       /** 主节点 */
       private Jedis master;
       /** 从节点,可能会有多个 */
       private List<Jedis> slaves = new ArrayList<>();

       public void setMaster(Jedis master) {
           this.master = master;
       }

       public void addSlaves(Jedis slave) {
           this.slaves.add(slave);
       }

       public String get(String key) {
           Jedis jedis = slaves.get((int)(Math.random() * slaves.size()));
           System.out.println(">> get:" + jedis);
           return jedis.get(key);
       }

       public void set(String key, String value) {
           master.set(key, value);
       }
   }

   public static void main(String[] args) {
       Set<String> sentinels = new HashSet<>();
       sentinels.add("127.0.0.1:26379");
       sentinels.add("127.0.0.1:26378");
       sentinels.add("127.0.0.1:26377");
       JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels);
       // 永远返回的都是 Master 连接
       Jedis master = pool.getResource();

       // 获取 Master 的地址信息
       HostAndPort hostAndPort = pool.getCurrentHostMaster();
       System.out.println("master = " + hostAndPort.getHost() + ":" + hostAndPort.getPort() + " " + master);

       // 构造我们自己的对象
       SentinelTest demo = new SentinelTest();
       MyJedis myJedis = demo.new MyJedis();
       myJedis.setMaster(master);

       // 解析主从信息,提取从节点信息
       Pattern pattern = Pattern.compile("^slave\\d+:ip=(.+),port=(\\d+),state=.+$");
       String[] infos = master.info("replication").split("(\\r\\n)|(\\n)");
       for(String info : infos) {
           Matcher matcher = pattern.matcher(info);
           if(matcher.find()) {
               Jedis slave = new Jedis(matcher.group(1), Integer.valueOf(matcher.group(2)));
               myJedis.addSlaves(slave);
           }
       }

       // 写入数据
       myJedis.set("title", "www.hxstrive.com");

       // 读取数据
       String result = myJedis.get("title");
       System.out.println("result = " + result);

       result = myJedis.get("title");
       System.out.println("result = " + result);
   }

}

上面代码中,通过自定义的 MyJedis 类来包装了 get 和 set 操作,在包装中的方法中,根据具体业务使用 Master 或 Slave 的连接来操作 Redis。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号