Redis哨兵及集群模式

哨兵模式

它是一种高可用的解决方案,sentinel本身是一个独立运行的进程,可以部署在其他与Redis集群可通讯的机器中监控Redis集群。

  • 监控:哨兵会定时监控redis是否良好运行。

  • 提醒:如果哨兵发现某个redis节点出现状况,能够通知另一个进程(如他的客户端)

  • 自动故障迁移:在master宕机后会进行主备切换。当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。如果master恢复了,那么会成为slave。
    Redis哨兵是一个分布式系统,采用Gossip协议接收master是否下线,采用投票协议决定是否执行故障迁移(在投票数超过所配置的值时会执行,执行时还需要得到一半以上的哨兵认可后才能执行),以及选择哪个slave作为master。

  • 主观下线:当一个哨兵进程监控master无法连接了,那么他认为master下线了。

  • 客观下线:当收到其他哨兵进程的master信息,判断是否真实下线。

每个哨兵向master、slave、其他哨兵每秒发送ping请求。
详细看这篇。

集群模式

由于哨兵不支持平滑的扩容,增加节点,那么自己要手动迁移数据。为此,Reids官方提供Redis Cluster实现分布式解决方案。分布式集群首要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整个数据的一个子集。
我觉得跟一致性hash一样。既然计算key的hash,获取到分片,那么集群模式缺陷就是无法进行key的批量操作了。因为这个key可能在不同的redis实例上。

集群的故障发现也是通过Gossip来通知各个redis节点的,两两节点互相ping、pong。
删除、增加节点则需要数据迁移。

介绍了哨兵和集群,但是不具体

源码分析-哨兵JedisSentinelPool

JedisSentinelPool创建时,需要指定master的name,传入几个哨兵节点,池配置,连接超时时间,密码等信息。
Redis哨兵及集群模式_第1张图片
先调用initSentinels方法,得到master信息,然后对master进行操作。哨兵机制:客户端通过哨兵节点来获得master信息来进行操作。

private HostAndPort initSentinels(Set 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());
		//通过mastername 从哨兵那获取 master节点
        List 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();
        }
      }
    }
	// master 为null 报错
    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...");
    //  创建多个监听器线程 不断的去从哨兵获取master,防止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;
  }

获取Jedis实例

// 获取Jedis实例时还要看一下数据源是否是同一个master,如果不是要归还连接
@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);
      }
    }
  }

集群模式JedisCluster

创建JedisCluster池子
在这里插入图片描述
这里new了一个JedisSlotBasedConnectionHandler,让我们看看他干什么用的。

public BinaryJedisCluster(Set jedisClusterNode, int connectionTimeout, int soTimeout, int maxAttempts, String password, String clientName, GenericObjectPoolConfig poolConfig) {
    this.connectionHandler = new JedisSlotBasedConnectionHandler(jedisClusterNode, poolConfig,
            connectionTimeout, soTimeout, password, clientName);
    this.maxAttempts = maxAttempts;
  }

源码看到他最终会创建一个JedisClusterInfoCache保存集群信息Cache,并调用了initializeSlotsCache方法。
Redis哨兵及集群模式_第2张图片
通过一开始传入的部分节点信息,连接上,然后调用cache.discoverClusterNodesAndSlots(jedis);方法。

通过一个节点,就能得到集群所有节点的信息,然后为每个节点初始化一个连接池

public void discoverClusterNodesAndSlots(Jedis jedis) {
    w.lock();

    try {
      reset();
      List slots = jedis.clusterSlots();

      for (Object slotInfoObj : slots) {
        List slotInfo = (List) slotInfoObj;

        if (slotInfo.size() <= MASTER_NODE_INDEX) {
          continue;
        }

        List slotNums = getAssignedSlotArray(slotInfo);

        // hostInfos
        int size = slotInfo.size();
        for (int i = MASTER_NODE_INDEX; i < size; i++) {
          List hostInfos = (List) slotInfo.get(i);
          if (hostInfos.size() <= 0) {
            continue;
          }

          HostAndPort targetNode = generateHostAndPort(hostInfos);
          setupNodeIfNotExist(targetNode);
          if (i == MASTER_NODE_INDEX) {
            assignSlotsToNode(slotNums, targetNode);
          }
        }
      }
    } finally {
      w.unlock();
    }
  }
 
  

Jedis源码

你可能感兴趣的:(redis)