【Jedis详解】Sentinel连接创建与关闭

本篇主要讲解Jedis源码中连接Sentinel获取连接池及其关闭的部分,Redis Sentinel的相关内容大家可以参考《Redis运维与开发》这本书,个人觉得是非常好的Redis入门书籍。

 

1. Sentinel连接池初始化

通过Jedis连接Sentinel我们需要先构建JedisSentinelPool,通过它再获取到Jedis实例,然后就可以进行Redis操作,代码大体如下:

JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels, new GenericObjectPoolConfig(), 1000);
Jedis jedis = pool.getResource();

这里我们先看下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, final int sentinelConnectionTimeout, 
    final int sentinelSoTimeout, final String sentinelPassword, 
    final String sentinelClientName) {

   this.poolConfig = poolConfig;
   this.connectionTimeout = connectionTimeout;
   this.soTimeout = soTimeout;
   this.password = password;
   this.database = database;
   this.clientName = clientName;
   this.sentinelConnectionTimeout = sentinelConnectionTimeout;
   this.sentinelSoTimeout = sentinelSoTimeout;
   this.sentinelPassword = sentinelPassword;
   this.sentinelClientName = sentinelClientName;

   HostAndPort master = initSentinels(sentinels, masterName);
   initPool(master);
}

 

其中重点在于initSentinels和initPool这两个方法,下面逐一介绍。

(1) initSentinels(Set<String> sentinels, final String masterName)

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.debug("Connecting to Sentinel {}", hap);

    Jedis jedis = null;
    try {
      jedis = new Jedis(hap.getHost(), hap.getPort(), sentinelConnectionTimeout, sentinelSoTimeout);
      if (sentinelPassword != null) {
        jedis.auth(sentinelPassword);
      }
      if (sentinelClientName != null) {
        jedis.clientSetname(sentinelClientName);
      }

      //通过"sentinel get-master-addr-by-name <masterName>"指令获取主节点ip和端口
      List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);

      // connected to sentinel...
      sentinelAvailable = true;

      if (masterAddr == null || masterAddr.size() != 2) {
        log.warn("Can not get master addr, master name: {}. Sentinel: {}", masterName, hap);
        continue;
      }

      master = toHostAndPort(masterAddr);
      log.debug("Found Redis master at {}", master);
      break;
    } catch (JedisException e) {
      // resolves #1036, it should handle JedisException there's another chance
      // of raising JedisDataException
      log.warn(
        "Cannot get master address from sentinel running @ {}. Reason: {}. Trying next one.", hap,
        e.toString());
    } 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...");

  /**
   * 为每个sentinel添加监听器:主要监听主节点IP端口信息变更,信息一旦变更则重新初始化连接池
   * 1. 主动获取:通过"sentinel get-master-addr-by-name <masterName>"指令获取;
   * 2. 消息通知:通过订阅"+switch-master"频道获取。
   */
  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;
}

 

该方法主要有两个作用:

1. 通过"sentinel get-master-addr-by-name <masterName>"指令获取主节点ip和端口。

2. 为每个sentinel添加监听器,主要监听主节点IP端口信息变更。

 

我们重点分析第二个作用,每个监听线程所执行的内容:

protected volatile Jedis j;
protected AtomicBoolean running = new AtomicBoolean(false);

@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;
      }

      // code for active refresh
      List<String> masterAddr = j.sentinelGetMasterAddrByName(masterName);
      if (masterAddr == null || masterAddr.size() != 2) {
        log.warn("Can not get master addr, master name: {}. Sentinel: {}:{}.", masterName, host, port);
      } else {
        initPool(toHostAndPort(masterAddr));
      }

      //订阅主节点变更频道(+switch-master)
      j.subscribe(new JedisPubSub() {
        @Override
        public void onMessage(String channel, String message) {
          log.debug("Sentinel {}:{} published: {}.", host, port, message);

          String[] switchMasterMsg = message.split(" ");

          //+switch-master事件消息格式:
          //<masterName> <old master ip> <old master port> <new master ip> <new master port>
          if (switchMasterMsg.length > 3) {

            if (masterName.equals(switchMasterMsg[0])) {
              initPool(toHostAndPort(Arrays.asList(switchMasterMsg[3], switchMasterMsg[4])));
            } else {
              log.debug(
                "Ignoring message on +switch-master for master name {}, our master name is {}",
                switchMasterMsg[0], masterName);
            }

          } else {
            log.error(
              "Invalid message received on Sentinel {}:{} on channel +switch-master: {}", host,
              port, message);
          }
        }
      }, "+switch-master");

    } catch (JedisException e) {

      if (running.get()) {
        log.error("Lost connection to Sentinel at {}:{}. Sleeping 5000ms and retrying.", host,
          port, e);
        try {
          Thread.sleep(subscribeRetryWaitTimeMillis);
        } catch (InterruptedException e1) {
          log.error("Sleep interrupted: ", e1);
        }
      } else {
        log.debug("Unsubscribing from Sentinel at {}:{}", host, port);
      }
    } finally {
      j.close();
    }
  }
}

 

在该任务循环中,其主要通过两种方式来发现主节点信息是否变更:

1. 主动获取:通过"sentinel get-master-addr-by-name <masterName>"指令获取;

2. 消息通知:通过订阅"+switch-master"频道获取。

 

当主节点发生变更时,会发布"+switch-master"事件,该事件的消息格式为:

<masterName> <old master ip> <old master port> <new master ip> <new master port>

 

Jeids通过switchMasterMsg信息重新初始化连接池,initPool方法我们下面说明。

(2)initPool(HostAndPort master)

private final Object initPoolLock = new Object();

private void initPool(HostAndPort master) {
  //同步锁保证每次只有一个线程进行初始化  
  synchronized(initPoolLock){
    if (!master.equals(currentHostMaster)) {
      currentHostMaster = master;
      if (factory == null) {
        factory = new JedisFactory(master.getHost(), master.getPort(), connectionTimeout,
            soTimeout, password, database, clientName);
        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);
    }
  }
}

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);
}

Jedis的连接池都是通过GenericObjectPool实现,本文暂不涉及,后续会专门介绍。

 

2. Sentinel获取连接

Jedis通过Sentinel连接池获取连接的代码如下:

Jedis jedis = pool.getResource();

JedisSentinelPool类通过重写父类Pool的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);
    }
  }
}

从上面的代码可以看出,获取到的是主节点的连接,也就是说,读写都在主节点上进行操作,从节点仅仅具有备份及故障转移的功能,并不参与数据操作。

 

3. Sentinel连接池关闭

@Override
public void destroy() {
  for (MasterListener m : masterListeners) {
    m.shutdown();
  }

  super.destroy();
}

该方法主要完成两件事:

(1)关闭所有sentinel的监听器,关闭代码如下:

public void shutdown() {
  try {
    log.debug("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.error("Caught exception while shutting down: ", e);
  }
}

(2)关闭连接池。

Jedis连接池涉及到GenericObjectPool,这个我们后面再详细分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值