• 分布式缓存技术之Redis_Redis集群连接及底层源码分析



    **本次源码分析基于: jedis-3.0.1 **

    1. Jedis 单点连接

      当是单点服务时,Java 连接Redis的客户端:

      Jedis jedis = null;
    
            try {
               jedis = new Jedis("192.168.237.130", 6379);
               jedis.hset("hashzz", "k1", "v1");
            } catch (Exception e) {
                System.out.println(e);
                e.printStackTrace();
            } finally {
                if (null != jedis) {
                    jedis.disconnect();
                }
            }
    

     或者

            JedisPool pool = null;
            try {
                pool = new JedisPool("192.168.237.130", 6379);
                pool.getResource().hset("hashzz", "k2", "v2");
            } catch (Exception e) {
                System.out.println(e);
                e.printStackTrace();
            } finally {
                if (null != pool) {
                    pool.close();
                }
            }
    

    2. Jedis 基于sentinel连接

    基本使用

     当为了避免单点故障而使用集群环境,哨兵sentinel会对master进行监听并在master无效时进行重新选举,此种情况不能在Java中直接指定master的IP,port,Jedis提供了sentinel的方式进行连接:

     // sentinel 哨兵
            // sentinel.conf 中配置的master名称
            String masterName = "mymaster";
            // sentinel 集群环境
            Set<String> sentinelIps = new HashSet<>();
            sentinelIps.add("192.168.237.129:26370");
            sentinelIps.add("192.168.237.130:26370");
            JedisSentinelPool sentinelPool = null;
    
            Jedis jedis = null;
    
            try {
                sentinelPool = new JedisSentinelPool(masterName, sentinelIps);
                jedis = sentinelPool.getResource();
    
                for (int i = 0; i< 10; i++) {
                    jedis.lpush("javaRedisClientList" + jedis.getClient().getHost(), new Integer(i).toString());
                }
    
            } catch (Exception e) {
                System.out.println(e);
                e.printStackTrace();
            } finally {
                if (jedis != null) {
                    jedis.disconnect();
                }
                if (sentinelPool != null) {
                    sentinelPool.close();
                }
            }
    

    源码分析

    • 首先查看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;
        // 初始化sentinel监听列表, 并返回当前master节点
        HostAndPort master = initSentinels(sentinels, masterName);
        // 初始化
        initPool(master);
      }
    
    • initSentinels()方法
    private HostAndPort initSentinels(Set<String> sentinels, final String masterName) {
    
        HostAndPort master = null;
        boolean sentinelAvailable = false;
    
        log.info("Trying to find master from available Sentinels...");
        // 首先遍历sentinel哨兵节点 
        for (String sentinel : sentinels) {
          final HostAndPort hap = HostAndPort.parseString(sentinel);
    
          log.debug("Connecting to Sentinel {}", hap);
    
          Jedis jedis = null;
          try {
            jedis = new Jedis(hap);
            // 从当前哨兵节点获取当前master
            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);
            // 找到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) {
          // 从哨兵列表中不能获取master 
          if (sentinelAvailable) {
            // 哨兵是可用的,则可能是master有问题
            // 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...");
          }
        }
       
        // 进行到这里,master和sentinel都是可用的
        log.info("Redis master running at " + master + ", starting Sentinel listeners...");
    
        for (String sentinel : sentinels) {
         // 为每个sentinel启动一个后台线程监听
          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;
      }
    
    • 内部类 MasterListener
     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;
              }
              
              /*
               * Added code for active refresh
               */
              // 根据哨兵sentinel连接获取当前master
              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)); 
              }
              // 基于redis 频道发布订阅(channel pub/sub)实现的java内部master选举的监听
              j.subscribe(new JedisPubSub() {
                @Override
                public void onMessage(String channel, String message) {
                  log.debug("Sentinel {}:{} published: {}.", host, port, message);
                  // 订阅的频道发来消息
                  String[] switchMasterMsg = message.split(" ");
    
                  if (switchMasterMsg.length > 3) {
    
                    if (masterName.equals(switchMasterMsg[0])) {
                      // 收到的消息 第一节数据 是 sentinel.conf 内配置的 master名称
                      // 根据订阅到的新的master信息, 重新初始化 master
                      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  
              }, "+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();
            }
          }
        }
    
        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);
          }
        }
      }
    

     可见Java Jedis连接Redis集群是基于哨兵集群的监听,首先传入哨兵的地址,根据哨兵信息及哨兵的内部通信得到当前的master连接redis客户端,然后后台为每个哨兵分配线程,线程内基于redis channel pub/sub来设立监听,如果有新的master选举,java内部订阅到消息之后重新对master进行初始化。

  • 相关阅读:
    mysql报Fatal error encountered during command execution的解决办法
    C语言之算法初步(汉诺塔--递归算法)
    C语言中变量的作用域和生命周期
    C语言数据在内存分配
    ~~~
    数据结构笔记
    SQL笔记
    Java零碎知识点
    如何让eclipse在程序修改后,点击运行可以自动保存。
    [转载] java中静态代码块的用法 static用法详解
  • 原文地址:https://www.cnblogs.com/Qkxh320/p/distributed_redis_jedis_sentinel.html
Copyright © 2020-2023  润新知