All Downloads are FREE. Search and download functionalities are using the official Maven repository.

redis.clients.jedis.JedisClusterInfoCache Maven / Gradle / Ivy

package redis.clients.jedis;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocketFactory;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.util.Pool;
import redis.clients.jedis.util.SafeEncoder;

public class JedisClusterInfoCache {
  private final Map nodes = new HashMap<>();
  private final Map slots = new HashMap<>();

  private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
  private final Lock r = rwl.readLock();
  private final Lock w = rwl.writeLock();
  private final Lock rediscoverLock = new ReentrantLock();

  private final GenericObjectPoolConfig poolConfig;
  private final JedisClientConfig clientConfig;

  private static final int MASTER_NODE_INDEX = 2;

  public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig, int timeout) {
    this(poolConfig, timeout, timeout, null, null);
  }

  public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig,
      final int connectionTimeout, final int soTimeout, final String password,
      final String clientName) {
    this(poolConfig, connectionTimeout, soTimeout, null, password, clientName);
  }

  public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig,
      final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,
      final String password, final String clientName) {
    this(poolConfig, connectionTimeout, soTimeout, infiniteSoTimeout, null, password, clientName);
  }

  public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig,
      final int connectionTimeout, final int soTimeout, final String user, final String password,
      final String clientName) {
    this(poolConfig, connectionTimeout, soTimeout, user, password, clientName, false, null, null,
        null, (HostAndPortMapper) null);
  }

  public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig,
      final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,
      final String user, final String password, final String clientName) {
    this(poolConfig, connectionTimeout, soTimeout, infiniteSoTimeout, user, password, clientName,
        false, null, null, null, (HostAndPortMapper) null);
  }

  /**
   * @deprecated This constructor will be removed in next major release.
   */
  @Deprecated
  public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig,
      final int connectionTimeout, final int soTimeout, final String password,
      final String clientName, boolean ssl, SSLSocketFactory sslSocketFactory,
      SSLParameters sslParameters, HostnameVerifier hostnameVerifier,
      JedisClusterHostAndPortMap hostAndPortMap) {
    this(poolConfig, connectionTimeout, soTimeout, null, password, clientName, ssl,
        sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMap);
  }

  public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig,
      final int connectionTimeout, final int soTimeout, final String password,
      final String clientName, boolean ssl, SSLSocketFactory sslSocketFactory,
      SSLParameters sslParameters, HostnameVerifier hostnameVerifier,
      HostAndPortMapper hostAndPortMap) {
    this(poolConfig, connectionTimeout, soTimeout, null, password, clientName, ssl,
        sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMap);
  }

  /**
   * @deprecated This constructor will be removed in next major release.
   */
  @Deprecated
  public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig,
      final int connectionTimeout, final int soTimeout, final String user, final String password,
      final String clientName, boolean ssl, SSLSocketFactory sslSocketFactory,
      SSLParameters sslParameters, HostnameVerifier hostnameVerifier,
      JedisClusterHostAndPortMap hostAndPortMap) {
    this(poolConfig, connectionTimeout, soTimeout, 0, user, password, clientName, ssl,
        sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMap);
  }

  public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig,
      final int connectionTimeout, final int soTimeout, final String user, final String password,
      final String clientName, boolean ssl, SSLSocketFactory sslSocketFactory,
      SSLParameters sslParameters, HostnameVerifier hostnameVerifier,
      HostAndPortMapper hostAndPortMap) {
    this(poolConfig, connectionTimeout, soTimeout, 0, user, password, clientName, ssl,
        sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMap);
  }

  /**
   * @deprecated This constructor will be removed in next major release.
   */
  @Deprecated
  public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig,
      final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,
      final String user, final String password, final String clientName, boolean ssl,
      SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
      HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap hostAndPortMap) {
    this(poolConfig, connectionTimeout, soTimeout, infiniteSoTimeout, user, password, clientName,
        ssl, sslSocketFactory, sslParameters, hostnameVerifier, (HostAndPortMapper) hostAndPortMap);
  }

  public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig,
      final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,
      final String user, final String password, final String clientName, boolean ssl,
      SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
      HostnameVerifier hostnameVerifier, HostAndPortMapper hostAndPortMap) {
    this(poolConfig, DefaultJedisClientConfig.builder()
        .connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(soTimeout)
        .blockingSocketTimeoutMillis(infiniteSoTimeout).user(user).password(password)
        .clientName(clientName).ssl(ssl).sslSocketFactory(sslSocketFactory)
        .sslParameters(sslParameters).hostnameVerifier(hostnameVerifier)
        .hostAndPortMapper(hostAndPortMap).build());
  }

  public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig,
      final JedisClientConfig clientConfig) {
    this.poolConfig = poolConfig;
    this.clientConfig = clientConfig;
  }

  public void discoverClusterNodesAndSlots(Jedis jedis) {
    List slots = jedis.clusterSlots();
    w.lock();
    try {
      reset();
      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.isEmpty()) {
            continue;
          }

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

  public void renewClusterSlots(Jedis jedis) {
    // If rediscovering is already in process - no need to start one more same rediscovering, just return
    if (rediscoverLock.tryLock()) {
      try {
        if (jedis != null) {
          try {
            discoverClusterSlots(jedis);
            return;
          } catch (JedisException e) {
            // try nodes from all pools
          }
        }

        for (JedisPool jp : getShuffledNodesPool()) {
          Jedis j = null;
          try {
            j = jp.getResource();
            discoverClusterSlots(j);
            return;
          } catch (JedisConnectionException e) {
            // try next nodes
          } finally {
            if (j != null) {
              j.close();
            }
          }
        }
      } finally {
        rediscoverLock.unlock();
      }
    }
  }

  private void discoverClusterSlots(Jedis jedis) {
    List slots = jedis.clusterSlots();
    w.lock();
    try {
      this.slots.clear();
      Set hostAndPortKeys = new HashSet<>();

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

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

        List slotNums = getAssignedSlotArray(slotInfo);

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

          HostAndPort targetNode = generateHostAndPort(hostInfos);
          hostAndPortKeys.add(getNodeKey(targetNode));
          setupNodeIfNotExist(targetNode);
          if (i == MASTER_NODE_INDEX) {
            assignSlotsToNode(slotNums, targetNode);
          }
        }
      }

      // Remove dead nodes according to the latest query
      Iterator> entryIt = nodes.entrySet().iterator();
      while (entryIt.hasNext()) {
        Entry entry = entryIt.next();
        if (!hostAndPortKeys.contains(entry.getKey())) {
          JedisPool pool = entry.getValue();
          try {
            if (pool != null) {
              pool.destroy();
            }
          } catch (Exception e) {
            // pass, may be this node dead
          }
          entryIt.remove();
        }
      }
    } finally {
      w.unlock();
    }
  }

  private HostAndPort generateHostAndPort(List hostInfos) {
    String host = SafeEncoder.encode((byte[]) hostInfos.get(0));
    int port = ((Long) hostInfos.get(1)).intValue();
    return new HostAndPort(host, port);
  }

  public JedisPool setupNodeIfNotExist(final HostAndPort node) {
    w.lock();
    try {
      String nodeKey = getNodeKey(node);
      JedisPool existingPool = nodes.get(nodeKey);
      if (existingPool != null) return existingPool;

      JedisPool nodePool = new JedisPool(poolConfig, node, clientConfig);
      nodes.put(nodeKey, nodePool);
      return nodePool;
    } finally {
      w.unlock();
    }
  }

  public void assignSlotToNode(int slot, HostAndPort targetNode) {
    w.lock();
    try {
      JedisPool targetPool = setupNodeIfNotExist(targetNode);
      slots.put(slot, targetPool);
    } finally {
      w.unlock();
    }
  }

  public void assignSlotsToNode(List targetSlots, HostAndPort targetNode) {
    w.lock();
    try {
      JedisPool targetPool = setupNodeIfNotExist(targetNode);
      for (Integer slot : targetSlots) {
        slots.put(slot, targetPool);
      }
    } finally {
      w.unlock();
    }
  }

  public JedisPool getNode(String nodeKey) {
    r.lock();
    try {
      return nodes.get(nodeKey);
    } finally {
      r.unlock();
    }
  }

  public JedisPool getSlotPool(int slot) {
    r.lock();
    try {
      return slots.get(slot);
    } finally {
      r.unlock();
    }
  }

  public Map getNodes() {
    r.lock();
    try {
      return new HashMap<>(nodes);
    } finally {
      r.unlock();
    }
  }

  public List getShuffledNodesPool() {
    r.lock();
    try {
      List pools = new ArrayList<>(nodes.values());
      Collections.shuffle(pools);
      return pools;
    } finally {
      r.unlock();
    }
  }

  /**
   * Clear discovered nodes collections and gently release allocated resources
   */
  public void reset() {
    w.lock();
    try {
      for (JedisPool pool : nodes.values()) {
        try {
          if (pool != null) {
            pool.destroy();
          }
        } catch (RuntimeException e) {
          // pass
        }
      }
      nodes.clear();
      slots.clear();
    } finally {
      w.unlock();
    }
  }

  public static String getNodeKey(HostAndPort hnp) {
    return hnp.getHost() + ":" + hnp.getPort();
  }

  /**
   * @deprecated This method will be removed in future.
   */
  @Deprecated
  public static String getNodeKey(Client client) {
    return client.getHost() + ":" + client.getPort();
  }

  /**
   * @deprecated This method will be removed in future.
   */
  @Deprecated
  public static String getNodeKey(Jedis jedis) {
    return getNodeKey(jedis.getClient());
  }

  private List getAssignedSlotArray(List slotInfo) {
    List slotNums = new ArrayList<>();
    for (int slot = ((Long) slotInfo.get(0)).intValue(); slot <= ((Long) slotInfo.get(1))
        .intValue(); slot++) {
      slotNums.add(slot);
    }
    return slotNums;
  }
}