jedis哨兵模式的redis组(集群),连接池实现。(客户端分片)

java 连接redis 我们都使用的 是jedis  ,对于redis这种频繁请求的场景我们一般需要对其池化避免重复创建,即创建一个连接池 ,打开jedis的 jar包我们发现,jedis对池已经有了相关的 实现,根据pom 依赖可以清楚的知道 这是基于common-pool2连接池实现的。jedis的jar包中包含了三个连接池 JedisPool与JedisSentinelPool与ShardedJedisPool 。那么 jedis 为什么会包含三种实现方式呢 ?其实归根结底还是因为redis环境的 不同。单节点模式还是哨兵模式、还是集群共享模式

首先JedisPool  我们可以清晰的看见这是为单节点模式实现的连接池 :

  public JedisPool(final GenericObjectPoolConfig poolConfig, final String host, int port,
      int timeout, final String password, final int database) {
    this(poolConfig, host, port, timeout, password, database, null);
  }

 再打开JedisSentinelPool,我们可以看到这是基于哨兵模式的单节点的 连接池实现:

  public JedisSentinelPool(String masterName, Set<String> sentinels,
      final GenericObjectPoolConfig poolConfig, final String password) {
    this(masterName, sentinels, poolConfig, Protocol.DEFAULT_TIMEOUT, password);
  }

 

再打开ShardedJedisPool  ,我们可以看到这是基于多个节点的 ,基于客户端分片的 redis组的连接池,具体原理是基于hash将不同的key 分配到不同的节点上,即提高了吞吐量,也可以一定程度避免单机风险。

  public ShardedJedisPool(final GenericObjectPoolConfig poolConfig, List<JedisShardInfo> shards,
      Hashing algo) {
    this(poolConfig, shards, algo, null);
  }

查看同时我们打开其节点信息JedisShardInfo,可以看见这是基于直连ip与端口的实现方式,生产环境一般不会直接提供这样的信息 。

public class JedisShardInfo extends ShardInfo<Jedis> {
  public String toString() {
    return host + ":" + port + "*" + getWeight();
  }

  private int timeout;
  private String host;
  private int port;
  private String password = null;
  private String name = null;

  public String getHost() {
    return host;
  }

  public int getPort() {
    return port;
  }

  public JedisShardInfo(String host) {
    super(Sharded.DEFAULT_WEIGHT);
    URI uri = URI.create(host);
    if (uri.getScheme() != null && uri.getScheme().equals("redis")) {
      this.host = uri.getHost();
      this.port = uri.getPort();
      this.password = uri.getUserInfo().split(":", 2)[1];
    } else {
      this.host = host;
      this.port = Protocol.DEFAULT_PORT;
    }
  }

在此,jedis链接池到上面就告一段落了 ,如果你的 环境信息与上面的一致,那么你可以放心的使用jedis连接池。

当然,ShardedJedisPool也会存在扩容的 问题详情见我的 下一篇文章。


 来到我们的正文:

 如果生产环境的redis是致力于高可用,我们一般是使用的哨兵模式,但是为避免单点风险,我们会创建多个redis服务使其形成一组redis集群(非集群模式),即是使哨兵模式,又是多节点的,那么你的链接池是否就应该是ShardedJedisSentinelPool了,这种JedisSentinelPool与ShardedJedisPool  构建的混合体。

那么其就应该是一下结构,以此来创建一个链接池:

    public ShardedJedisSentinelPoolExt(Set<String> masters, Set<String> sentinels, GenericObjectPoolConfig poolConfig, String password) {
        this((Set)masters, (Set)sentinels, poolConfig, 2000, password);
    }

 在此基础上按照JedisSentinel实现哨兵的加载与ShardedJedis实现客户端分片。

示例代码如下:

public class ShardedJedisSentinelPoolExt extends Pool<ShardedJedis> {
    private static final int MAX_RETRY_SENTINEL = 10;
    private static final Logger logger = LoggerFactory.getLogger(ShardedJedisSentinelPoolExt.class);
    private GenericObjectPoolConfig poolConfig;
    private int timeout;
    private int sentinelRetry;
    private String password;
    private Set<ShardedJedisSentinelPoolExt.MasterListener> masterListeners;
    private volatile List<HostAndPort> currentHostMasters;
    private String redisMasterName;
    private static Pattern pattern = Pattern.compile("^[A-Z0-9_]{5,200}\\$[0-9]{2,4}-[0-9]{2,4}([A-Z0-9_]{5,200})?");

    public ShardedJedisSentinelPoolExt(Set<String> masters, Set<String> sentinels) {
        this(masters, sentinels, new GenericObjectPoolConfig(), 2000, (String)null, 0);
    }

    public ShardedJedisSentinelPoolExt(Set<String> masters, Set<String> sentinels, String password) {
        this((Set)masters, (Set)sentinels, new GenericObjectPoolConfig(), 2000, password);
    }

    public ShardedJedisSentinelPoolExt(GenericObjectPoolConfig poolConfig, Set<String> masters, Set<String> sentinels) {
        this(masters, sentinels, poolConfig, 2000, (String)null, 0);
    }

    public ShardedJedisSentinelPoolExt(String mastersStr, String sentinelsStr, GenericObjectPoolConfig poolConfig, int timeout, String password) {
        this.timeout = 2000;
        this.sentinelRetry = 0;
        this.masterListeners = new HashSet();
        String[] splitMasters = mastersStr.split(",");
        String[] splitSentinels = sentinelsStr.split(",");
        List<String> masters = new ArrayList();
        String[] var9 = splitMasters;
        int var10 = splitMasters.length;

        for(int var11 = 0; var11 < var10; ++var11) {
            String splitMaster = var9[var11];
            List<String> stringList = convertToMatch(splitMaster);
            if (stringList != null) {
                masters.addAll(stringList);
            }
        }

        Set<String> setSentinels = new HashSet(Arrays.asList(splitSentinels));
        this.poolConfig = poolConfig;
        this.timeout = timeout;
        this.password = password;
        this.redisMasterName = mastersStr;
        List<HostAndPort> masterList = this.initSentinels(setSentinels, masters);
        this.initPool(masterList);
    }

    public ShardedJedisSentinelPoolExt(String mastersStr, Set<String> sentinels, GenericObjectPoolConfig poolConfig, int timeout, String password) {
        this.timeout = 2000;
        this.sentinelRetry = 0;
        this.masterListeners = new HashSet();
        String[] splitMasters = mastersStr.split(",");
        Set<String> masters = new HashSet();
        String[] var8 = splitMasters;
        int var9 = splitMasters.length;

        for(int var10 = 0; var10 < var9; ++var10) {
            String splitMaster = var8[var10];
            List<String> stringList = convertToMatch(splitMaster);
            if (stringList != null) {
                masters.addAll(stringList);
            }
        }

        this.poolConfig = poolConfig;
        this.timeout = timeout;
        this.password = password;
        this.redisMasterName = mastersStr;
        List<String> masterStrList = new ArrayList(masters);
        Collections.sort(masterStrList);
        List<HostAndPort> masterList = this.initSentinels(sentinels, masterStrList);
        this.initPool(masterList);
    }

    public ShardedJedisSentinelPoolExt(Set<String> masters, Set<String> sentinels, GenericObjectPoolConfig poolConfig, int timeout, String password) {
        this(masters, sentinels, poolConfig, timeout, password, 0);
    }

    public ShardedJedisSentinelPoolExt(Set<String> masters, Set<String> sentinels, GenericObjectPoolConfig poolConfig, int timeout) {
        this(masters, sentinels, poolConfig, timeout, (String)null, 0);
    }

    public ShardedJedisSentinelPoolExt(Set<String> masters, Set<String> sentinels, GenericObjectPoolConfig poolConfig, String password) {
        this((Set)masters, (Set)sentinels, poolConfig, 2000, password);
    }

    public ShardedJedisSentinelPoolExt(Set<String> masters, Set<String> sentinels, GenericObjectPoolConfig poolConfig, int timeout, String password, int database) {
        this.timeout = 2000;
        this.sentinelRetry = 0;
        this.masterListeners = new HashSet();
        this.poolConfig = poolConfig;
        this.timeout = timeout;
        this.password = password;
        List<String> masterStrList = new ArrayList(masters);
        Collections.sort(masterStrList);
        List<HostAndPort> masterList = this.initSentinels(sentinels, masterStrList);
        this.initPool(masterList);
    }

    public void destroy() {
        Iterator var1 = this.masterListeners.iterator();

        while(var1.hasNext()) {
            ShardedJedisSentinelPoolExt.MasterListener m = (ShardedJedisSentinelPoolExt.MasterListener)var1.next();
            m.shutdown();
        }

        super.destroy();
    }

    public List<HostAndPort> getCurrentHostMaster() {
        return this.currentHostMasters;
    }

    private void initPool(List<HostAndPort> masters) {
        if (!masterEquals(this.currentHostMasters, masters)) {
            StringBuilder sb = new StringBuilder();
            Iterator var3 = masters.iterator();

            while(var3.hasNext()) {
                HostAndPort master = (HostAndPort)var3.next();
                sb.append(master.toString());
                sb.append(" ");
            }

            logger.info("Created ShardedJedisPool to master at [" + sb.toString() + "]");
            List<JedisShardInfo> shardMasters = this.makeShardInfoList(masters);
            this.initPool(this.poolConfig, new ShardedJedisSentinelPoolExt.ShardedJedisFactory(shardMasters, Hashing.MURMUR_HASH, (Pattern)null));
            this.currentHostMasters = masters;
        }

    }

    private static boolean masterEquals(List<HostAndPort> currentShardMasters, List<HostAndPort> shardMasters) {
        if (currentShardMasters != null && shardMasters != null && currentShardMasters.size() == shardMasters.size()) {
            for(int i = 0; i < currentShardMasters.size(); ++i) {
                if (!((HostAndPort)currentShardMasters.get(i)).equals(shardMasters.get(i))) {
                    return false;
                }
            }

            return true;
        } else {
            return false;
        }
    }

    private List<JedisShardInfo> makeShardInfoList(List<HostAndPort> masters) {
        List<JedisShardInfo> shardMasters = new ArrayList();
        Iterator var3 = masters.iterator();

        while(var3.hasNext()) {
            HostAndPort master = (HostAndPort)var3.next();
            JedisShardInfo jedisShardInfo = new JedisShardInfo(master.getHost(), master.getPort(), this.timeout);
            jedisShardInfo.setPassword(this.password);
            shardMasters.add(jedisShardInfo);
        }

        return shardMasters;
    }

    private List<HostAndPort> initSentinels(Set<String> sentinels, List<String> masters) {
        Map<String, HostAndPort> masterMap = new HashMap();
        List<HostAndPort> shardMasters = new ArrayList();
        logger.info("Trying to find all master:" + masters + "from available Sentinels:" + sentinels);
        Iterator var5 = masters.iterator();

        boolean fetched;
        do {
            String masterName;
            if (!var5.hasNext()) {
                if (!masters.isEmpty() && masters.size() == shardMasters.size()) {
                    var5 = sentinels.iterator();

                    while(var5.hasNext()) {
                        masterName = (String)var5.next();
                        String threadName = "master-listener-sentinel-" + masterName;
                        logger.info("Starting Sentinel listeners thread " + masterName);
                        HostAndPort hap = toHostAndPort(Arrays.asList(masterName.split(":")));
                        ShardedJedisSentinelPoolExt.MasterListener masterListener = new ShardedJedisSentinelPoolExt.MasterListener(masters, hap.getHost(), hap.getPort(), threadName);
                        this.masterListeners.add(masterListener);
                        masterListener.start();
                    }
                }

                return shardMasters;
            }

            masterName = (String)var5.next();
            HostAndPort master = null;
            fetched = false;

            while(!fetched && this.sentinelRetry < 10) {
                Iterator var9 = sentinels.iterator();

                while(var9.hasNext()) {
                    String sentinel = (String)var9.next();
                    HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":")));
                    logger.info("Connecting to Sentinel " + hap);

                    try {
                        Jedis jedis = new Jedis(hap.getHost(), hap.getPort());
                        Throwable var13 = null;

                        try {
                            master = (HostAndPort)masterMap.get(masterName);
                            if (master == null) {
                                List<String> hostAndPort = jedis.sentinelGetMasterAddrByName(masterName);
                                if (hostAndPort != null && !hostAndPort.isEmpty()) {
                                    master = toHostAndPort(hostAndPort);
                                    logger.info("Found Redis master " + masterName + " at " + master);
                                    shardMasters.add(master);
                                    masterMap.put(masterName, master);
                                    fetched = true;
                                    jedis.disconnect();
                                    break;
                                }
                            }
                        } catch (Throwable var27) {
                            var13 = var27;
                            throw var27;
                        } finally {
                            if (jedis != null) {
                                if (var13 != null) {
                                    try {
                                        jedis.close();
                                    } catch (Throwable var26) {
                                        var13.addSuppressed(var26);
                                    }
                                } else {
                                    jedis.close();
                                }
                            }

                        }
                    } catch (JedisException var29) {
                        logger.error("Cannot connect to sentinel :{} ,Trying next one sentinel , errorMsg:{}", new Object[]{hap, var29.getMessage(), var29});
                    }
                }

                if (null == master) {
                    try {
                        logger.warn("All sentinels down, cannot determine where is " + masterName + " master is running... sleeping 1000ms, Will try again.");
                        Thread.sleep(1000L);
                    } catch (InterruptedException var25) {
                        logger.error(var25.getMessage(), var25);
                    }

                    fetched = false;
                    ++this.sentinelRetry;
                }
            }
        } while(fetched);

        logger.error("All sentinels down and try 10 times, Abort.");
        throw new JedisConnectionException("Cannot connect all sentinels, Abort.");
    }

    private static HostAndPort toHostAndPort(List<String> getMasterAddrByNameResult) {
        String host = (String)getMasterAddrByNameResult.get(0);
        int port = Integer.parseInt((String)getMasterAddrByNameResult.get(1));
        return new HostAndPort(host, port);
    }

    public static List<String> convertToMatch(String str) {
        List<String> stringList = new ArrayList();
        Matcher matcher = pattern.matcher(str);
        if (!matcher.matches()) {
            stringList.add(str);
            return stringList;
        } else {
            String prefix = str.substring(0, str.indexOf("$"));
            String suffix = str.substring(str.indexOf("$") + 1);
            String[] split = suffix.split("-");
            if (split.length != 2) {
                return Collections.emptyList();
            } else {
                String startStr = split[0];
                boolean isBeginWithZero = startStr.indexOf("0") == 0;
                Integer beginNumber = Integer.valueOf(startStr);
                String endStr = split[1];
                int endStrLength = endStr.length();
                int lastNumberIndex = 0;

                for(int i = 0; i < endStrLength; ++i) {
                    char c = endStr.charAt(i);
                    if (!Character.isDigit(c)) {
                        lastNumberIndex = i;
                        break;
                    }
                }

                String endSuffix = "";
                if (lastNumberIndex > 0) {
                    endSuffix = endStr.substring(lastNumberIndex);
                    endStr = endStr.substring(0, lastNumberIndex);
                }

                for(Integer endNumber = Integer.valueOf(endStr); beginNumber <= endNumber; beginNumber = beginNumber + 1) {
                    StringBuilder stringBuilder = new StringBuilder(prefix);
                    if (isBeginWithZero && beginNumber < 10) {
                        stringBuilder.append("0");
                    }

                    stringBuilder.append(beginNumber);
                    stringBuilder.append(endSuffix);
                    stringList.add(stringBuilder.toString());
                }

                return stringList;
            }
        }
    }

    public String getRedisMasterName() {
        return this.redisMasterName;
    }

    public void setRedisMasterName(String redisMasterName) {
        this.redisMasterName = redisMasterName;
    }

    public GenericObjectPoolConfig getPoolConfig() {
        return this.poolConfig;
    }

    public void setPoolConfig(GenericObjectPoolConfig poolConfig) {
        this.poolConfig = poolConfig;
    }

    protected class MasterListener extends Thread {
        protected List<String> masters;
        protected String host;
        protected int port;
        protected long subscribeRetryWaitTimeMillis;
        protected Jedis jedis;
        protected AtomicBoolean running;

        public MasterListener(List<String> masters, String host, int port) {
            this.subscribeRetryWaitTimeMillis = 5000L;
            this.running = new AtomicBoolean(false);
            this.masters = masters;
            this.host = host;
            this.port = port;
        }

        public MasterListener(List<String> masters, String host, int port, String threadName) {
            super(threadName);
            this.subscribeRetryWaitTimeMillis = 5000L;
            this.running = new AtomicBoolean(false);
            this.masters = masters;
            this.host = host;
            this.port = port;
        }

        public MasterListener(List<String> masters, String host, int port, long subscribeRetryWaitTimeMillis) {
            this(masters, host, port);
            this.subscribeRetryWaitTimeMillis = subscribeRetryWaitTimeMillis;
        }

        public void run() {
            this.running.set(true);
            if (this.running.get()) {
                do {
                    this.jedis = new Jedis(this.host, this.port);

                    try {
                        this.jedis.subscribe(new ShardedJedisSentinelPoolExt.JedisPubSubAdapter() {
                            public void onMessage(String channel, String message) {
                                ShardedJedisSentinelPoolExt.logger.info("Sentinel " + MasterListener.this.host + ":" + MasterListener.this.port + " published: " + message + ".");
                                String[] switchMasterMsg = message.split(" ");
                                if (switchMasterMsg.length > 3) {
                                    int index = MasterListener.this.masters.indexOf(switchMasterMsg[0]);
                                    if (index >= 0) {
                                        HostAndPort newHostMaster = ShardedJedisSentinelPoolExt.toHostAndPort(Arrays.asList(switchMasterMsg[3], switchMasterMsg[4]));
                                        List<HostAndPort> newHostMasters = new ArrayList();

                                        for(int i = 0; i < MasterListener.this.masters.size(); ++i) {
                                            newHostMasters.add((Object)null);
                                        }

                                        Collections.copy(newHostMasters, ShardedJedisSentinelPoolExt.this.currentHostMasters);
                                        newHostMasters.set(index, newHostMaster);
                                        ShardedJedisSentinelPoolExt.this.initPool(newHostMasters);
                                    } else {
                                        StringBuilder sb = new StringBuilder();
                                        Iterator var9 = MasterListener.this.masters.iterator();

                                        while(var9.hasNext()) {
                                            String masterName = (String)var9.next();
                                            sb.append(masterName);
                                            sb.append(",");
                                        }

                                        ShardedJedisSentinelPoolExt.logger.info("Ignoring message on +switch-master for master name " + switchMasterMsg[0] + ", our monitor master name are [" + sb + "]");
                                    }
                                } else {
                                    ShardedJedisSentinelPoolExt.logger.warn("Invalid message received on Sentinel " + MasterListener.this.host + ":" + MasterListener.this.port + " on channel +switch-master: " + message);
                                }

                            }
                        }, new String[]{"+switch-master"});
                    } catch (JedisConnectionException var4) {
                        ShardedJedisSentinelPoolExt.logger.error("Lost connection to Sentinel at {}:{},{}", new Object[]{this.host, this.port, var4.getMessage(), var4});
                        if (this.running.get()) {
                            ShardedJedisSentinelPoolExt.logger.warn("Lost connection to Sentinel at " + this.host + ":" + this.port + ". Sleeping 5000ms and retrying.");

                            try {
                                Thread.sleep(this.subscribeRetryWaitTimeMillis);
                            } catch (InterruptedException var3) {
                                ShardedJedisSentinelPoolExt.logger.error(var3.getMessage(), var3);
                            }
                        } else {
                            ShardedJedisSentinelPoolExt.logger.error("Unsubscribing from Sentinel at " + this.host + ":" + this.port);
                        }
                    }
                } while(this.running.get());
            }

        }

        public void shutdown() {
            try {
                ShardedJedisSentinelPoolExt.logger.info("Shutting down listener on " + this.host + ":" + this.port);
                this.running.set(false);
                this.jedis.disconnect();
            } catch (Exception var2) {
                ShardedJedisSentinelPoolExt.logger.error("Caught exception while shutting down: " + var2.getMessage());
                throw new CcspRuntimeException(var2);
            }
        }
    }

    protected class JedisPubSubAdapter extends JedisPubSub {
        protected JedisPubSubAdapter() {
        }
    }

    protected static class ShardedJedisFactory implements PooledObjectFactory<ShardedJedis> {
        private List<JedisShardInfo> shards;
        private Hashing algo;
        private Pattern keyTagPattern;

        public ShardedJedisFactory(List<JedisShardInfo> shards, Hashing algo, Pattern keyTagPattern) {
            this.shards = shards;
            this.algo = algo;
            this.keyTagPattern = keyTagPattern;
        }

        public PooledObject<ShardedJedis> makeObject() throws Exception {
            ShardedJedis jedis = new ShardedJedis(this.shards, this.algo, this.keyTagPattern);
            return new DefaultPooledObject(jedis);
        }

        public void destroyObject(PooledObject<ShardedJedis> pooledShardedJedis) throws Exception {
            ShardedJedis shardedJedis = (ShardedJedis)pooledShardedJedis.getObject();
            Iterator var3 = shardedJedis.getAllShards().iterator();

            while(var3.hasNext()) {
                Jedis jedis = (Jedis)var3.next();

                try {
                    jedis.quit();
                } catch (Exception var7) {
                    ShardedJedisSentinelPoolExt.logger.error(var7.getMessage(), var7);
                }

                try {
                    jedis.disconnect();
                } catch (Exception var6) {
                    ShardedJedisSentinelPoolExt.logger.error(var6.getMessage(), var6);
                }
            }

        }

        public boolean validateObject(PooledObject<ShardedJedis> pooledShardedJedis) {
            try {
                ShardedJedis jedis = (ShardedJedis)pooledShardedJedis.getObject();
                Iterator var3 = jedis.getAllShards().iterator();

                Jedis shard;
                do {
                    if (!var3.hasNext()) {
                        return true;
                    }

                    shard = (Jedis)var3.next();
                } while("PONG".equals(shard.ping()));

                return false;
            } catch (Exception var5) {
                ShardedJedisSentinelPoolExt.logger.error(var5.getMessage(), var5);
                return false;
            }
        }

        public void activateObject(PooledObject<ShardedJedis> p) throws Exception {
        }

        public void passivateObject(PooledObject<ShardedJedis> p) throws Exception {
        }
    }
}

 

posted @ 2019-10-09 14:13  别问1991  阅读(5170)  评论(0编辑  收藏  举报