jedis源码分析(五)-哨兵模式

本文介绍了Redis哨兵模式的工作原理,包括如何通过哨兵节点监控主节点状态,并在主节点故障时进行选举产生新的主节点。同时展示了如何使用JedisSentinelPool连接哨兵,并通过代码示例详细解释了其内部实现机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

4,哨兵模式:
一个master节点,多个salver节点,从节点默认只能查询,一个哨兵节点,哨兵节点负责监控主节点状态,在主节点服务挂掉之后可以在从节点中选举从新的主节点使缓存服务立刻恢复工作;通过jedis自带的JedisSentinelPool实现,但是JedisSentinelPool只支持哨兵监听一个主节点,如果我们需要更多的主节点支持,目前jedis版本自带的JedisSentinelPool无法很好的支持,所以我们需要自己改造支持多个主节点的pool连接池,
目前的jedis版本自带支持哨兵监听一个主master节点的连接池方式JedisSentinelPool,通过jedisSentinelPool连接哨兵的demo示例:
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;
public class JedisSentinelPoolUtil {
       private static Logger logger = Logger.getLogger("JedisPoolUtil");
       private static  JedisSentinelPool pool = null;
 
       /**
        * 创建连接池       
         */
       static {
              JedisPoolConfig config = new JedisPoolConfig();
              config.setMaxIdle(10);
              config.setMaxTotal(200);
              config.setMaxWaitMillis(1000 * 10);
              config.setTestOnBorrow(true);
              Set<String> sentinels = new HashSet<String>();
              sentinels.add("127.0.0.1:4200");
              pool = new JedisSentinelPool("master1", sentinels, config);
       }
       /**
        * 获取连接池jedis 对象
        * @return
        */
       public static Jedis getJedis() {
              return pool.getResource();
       }
       public static String set(String key, String value) {
              Jedis jedis = null;
              String result = null;
              try {
                     if (jedis == null) {
                           jedis = pool.getResource();
                     }
                     result = jedis.set(key, value);
              } catch (Exception e) {
                     logger.info(e.getMessage());
              } finally {
                     jedis.close();
                     // pool.destroy();
              }
              return result;
       }
       public static String get(String key) {
              Jedis jedis = null;
              String result = null;
              try {
                     if (jedis == null) {
                           jedis = pool.getResource();
                     }
                     result = jedis.get(key);
              } catch (Exception e) {
                     logger.info(e.getMessage());
              } finally {
                     jedis.close();
              }
              return result;
       }
       public static void main(String[] args) {
              for (int i = 0; i < 100; i++) {
                     JedisSentinelPoolUtil.set("key" + i, "value" + i);
                     System.out.println(JedisSentinelPoolUtil.get("key" + i) + " ");
              }
       }
}

查看JedisSentinelPool源码的构造方法:
public JedisSentinelPool(String masterName, Set<String> sentinels,
      final GenericObjectPoolConfig poolConfig, final int connectionTimeout, final int soTimeout,
      final String password, final int database, final String clientName) {
    this.poolConfig = poolConfig;
    this.connectionTimeout = connectionTimeout;
    this.soTimeout = soTimeout;
    this.password = password;
    this.database = database;
    this.clientName = clientName;
    HostAndPort master = initSentinels(sentinels, masterName);
    initPool(master);
  }
可以看到源码中在构造方法中通过initSentinel方法
private HostAndPort initSentinels(Set<String> sentinels, final String masterName) {
    HostAndPort master = null;
    boolean sentinelAvailable = false;
    log.info("Trying to find master from available Sentinels...");
    for (String sentinel : sentinels) {
      final HostAndPort hap = HostAndPort.parseString(sentinel);
      log.fine("Connecting to Sentinel " + hap);
      Jedis jedis = null;
      try {
        jedis = new Jedis(hap.getHost(), hap.getPort());
        List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);
        // connected to sentinel...
        sentinelAvailable = true;
        if (masterAddr == null || masterAddr.size() != 2) {
          log.warning("Can not get master addr, master name: " + masterName + ". Sentinel: " + hap
              + ".");
          continue;
        }
        master = toHostAndPort(masterAddr);
        log.fine("Found Redis master at " + master);
        break;
      } catch (JedisException e) {
        // resolves #1036, it should handle JedisException there's another chance
        // of raising JedisDataException
        log.warning("Cannot get master address from sentinel running @ " + hap + ". Reason: " + e
            + ". Trying next one.");
      } finally {
        if (jedis != null) {
          jedis.close();
        }
      }
    }
    if (master == null) {
      if (sentinelAvailable) {
        // can connect to sentinel, but master name seems to not monitored
        throw new JedisException("Can connect to sentinel, but " + masterName + " seems to be not monitored...");
      } else {
        throw new JedisConnectionException("All sentinels down, cannot determine where is " + masterName + " master is running...");
      }
    }
    log.info("Redis master running at " + master + ", starting Sentinel listeners...");
    for (String sentinel : sentinels) {
      final HostAndPort hap = HostAndPort.parseString(sentinel);
      MasterListener masterListener = new MasterListener(masterName, hap.getHost(), hap.getPort());
      // whether MasterListener threads are alive or not, process can be stopped
      masterListener.setDaemon(true);
      masterListeners.add(masterListener);
      masterListener.start();
    }
    return master;
  }
传入哨兵地址集合sentinels和主节名称masterName参数,
通过循环哨兵列表,在循环体内然后通过哨兵的jedis对象调用sentinelGetMasterAddrByName方法获取哨兵监控的主节点的地址信息host和port信息放入list<String>类型的masterAddr中
通过masterAddr传入toHostAndPort方法获取到一个简单封装了主节点地址host和端口port属性的HostAndPort类master

这里在得到master对象后,再次循环了哨兵列表得到哨兵的HostAndPort对象,这里创建一个MasterListener线程来通过哨兵监听master节点变化,如果主节点发生变化,重新获取选举的新的主节点信息,重新初始化连接池。
MaserListener实现方法
  protected class MasterListener extends Thread {
    protected String masterName;
    protected String host;
    protected int port;
    protected long subscribeRetryWaitTimeMillis = 5000;
    protected volatile Jedis j;
    protected AtomicBoolean running = new AtomicBoolean(false);
    protected MasterListener() {
    }
    public MasterListener(String masterName, String host, int port) {
      super(String.format("MasterListener-%s-[%s:%d]", masterName, host, port));
      this.masterName = masterName;
      this.host = host;
      this.port = port;
    }
    public MasterListener(String masterName, String host, int port,
        long subscribeRetryWaitTimeMillis) {
      this(masterName, host, port);
      this.subscribeRetryWaitTimeMillis = subscribeRetryWaitTimeMillis;
    }
    @Override
    public void run() {
      running.set(true);
      while (running.get()) {
        j = new Jedis(host, port);
        try {
          // double check that it is not being shutdown
          if (!running.get()) {
            break;
          }
          j.subscribe(new JedisPubSub() {
            @Override
            public void onMessage(String channel, String message) {
              log.fine("Sentinel " + host + ":" + port + " published: " + message + ".");
              String[] switchMasterMsg = message.split(" ");
              if (switchMasterMsg.length > 3) {
                if (masterName.equals(switchMasterMsg[0])) {
                  initPool(toHostAndPort(Arrays.asList(switchMasterMsg[3], switchMasterMsg[4])));
                } else {
                  log.fine("Ignoring message on +switch-master for master name "
                      + switchMasterMsg[0] + ", our master name is " + masterName);
                }
              } else {
                log.severe("Invalid message received on Sentinel " + host + ":" + port
                    + " on channel +switch-master: " + message);
              }
            }
          }, "+switch-master");
        } catch (JedisConnectionException e) {
          if (running.get()) {
            log.log(Level.SEVERE, "Lost connection to Sentinel at " + host + ":" + port
                + ". Sleeping 5000ms and retrying.", e);
            try {
              Thread.sleep(subscribeRetryWaitTimeMillis);
            } catch (InterruptedException e1) {
              log.log(Level.SEVERE, "Sleep interrupted: ", e1);
            }
          } else {
            log.fine("Unsubscribing from Sentinel at " + host + ":" + port);
          }
        } finally {
          j.close();
        }
      }
    }
    public void shutdown() {
      try {
        log.fine("Shutting down listener on " + host + ":" + port);
        running.set(false);
        // This isn't good, the Jedis object is not thread safe
        if (j != null) {
          j.disconnect();
        }
      } catch (Exception e) {
        log.log(Level.SEVERE, "Caught exception while shutting down: ", e);
      }
    }
  }
run方法中通过哨兵的jedis对象subscribe方法监听广播消息,并用当前masterName与广播消息中的节点名称比较,如果相等说明产生新的主节点,调用org.apache.commons.pool2.impl.GenericObjectPoolGenericObjectPool方法返回连接池。只有产生新的主节点才会广播信息。

将master对象传入initPool初始化连接池方法中。这里创建一个JedisFactory工厂类,
  private void initPool(HostAndPort master) {
    if (!master.equals(currentHostMaster)) {
      currentHostMaster = master;
      if (factory == null) {
        factory = new JedisFactory(master.getHost(), master.getPort(), connectionTimeout,
            soTimeout, password, database, clientName, false, null, null, null);
        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);
    }
  }

得到jedisFactory工厂类之后,调用实现了Closeable接口的Pool<T>类的initPool方法
 public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) {
    if (this.internalPool != null) {
      try {
        closeInternalPool();
      } catch (Exception e) {
      }
    }
    this.internalPool = new GenericObjectPool<T>(factory, poolConfig);
  }
这里直接new一个org.apache.commons.pool2.impl.GenericObjectPool实现的GenericObjectPool方法返回连接池。
 通过得到的pool连接池调用getResource()得到一个Jedis对象
public static Jedis getJedis() {
    return pool.getResource();
}

在整个过程中有两个比较重要点是一个是创建JedisFactory工厂类对象,一个是MasterLinenter线程监听类;JedisFactory工厂类负责jedis对象的创建、销毁、激活、验证等操作。masterListener监听类负责监听哨兵的广播消息,是否有新的master节点产生,如果生产新的master节点并负责创建新的连接池。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值