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

com.couchbase.client.vbucket.VBucketNodeLocator Maven / Gradle / Ivy

There is a newer version: 1.4.13
Show newest version
/**
 * Copyright (C) 2009-2013 Couchbase, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALING
 * IN THE SOFTWARE.
 */

package com.couchbase.client.vbucket;

import com.couchbase.client.vbucket.config.Config;
import com.couchbase.client.vbucket.config.ConfigDifference;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import com.couchbase.client.vbucket.config.VBucket;
import net.spy.memcached.MemcachedNode;
import net.spy.memcached.NodeLocator;
import net.spy.memcached.compat.SpyObject;

/**
 * Implementation of the {@link NodeLocator} interface that contains vbucket
 * hashing methods.
 */
public class VBucketNodeLocator extends SpyObject implements NodeLocator {

  private final AtomicReference fullConfig;

  /**
   * Construct a VBucketNodeLocator over the given JSON configuration string.
   *
   * @param nodes
   * @param jsonConfig
   */
  public VBucketNodeLocator(List nodes, Config jsonConfig) {
    super();
    fullConfig = new AtomicReference();
    fullConfig.set(new TotalConfig(jsonConfig,
            fillNodesEntries(jsonConfig, nodes)));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public MemcachedNode getPrimary(String k) {
    TotalConfig totConfig = fullConfig.get();
    Config config = totConfig.getConfig();
    Map nodesMap = totConfig.getNodesMap();
    int vbucket = config.getVbucketByKey(k);
    int serverNumber = config.getMaster(vbucket);

    if(serverNumber == -1) {
      getLogger().warn("The key "+ k +" pointed to vbucket "+ vbucket
        + ", for which no server is responsible in the cluster map (-1). This "
        + "can be an indication that either no replica is defined for a "
        + "failed server or more nodes have been failed over than replicas "
        + "defined.");
      return null;
    }

    String server = config.getServer(serverNumber);
    // choose appropriate MemcachedNode according to config data
    MemcachedNode pNode = nodesMap.get(server);
    if (pNode == null) {
      getLogger().error("The node locator does not have a primary for key"
        + " %s.  Wanted vbucket %s which should be on server %s.", k,
        vbucket, server);
      getLogger().error("List of nodes has %s entries:", nodesMap.size());
      Set keySet = nodesMap.keySet();
      Iterator iterator = keySet.iterator();
      while (iterator.hasNext()) {
        String anode = iterator.next();
        getLogger().error("MemcachedNode for %s is %s", anode,
          nodesMap.get(anode));
      }
      Collection nodes = nodesMap.values();
      for (MemcachedNode node : nodes) {
        getLogger().error(node);
      }
    }
    assert (pNode != null);
    return pNode;
  }

  /**
   * Return a replica node for the given key and replica index.
   *
   * Based on the ReplicaIndex ID given, this method calculates the
   * replica node. It works similar to the getMaster method, but has the
   * additional capability to find the node based on the given replica
   * index.
   *
   * @param key the key to find the node for.
   * @param index the Nth replica number
   * @return the node where the given replica exists
   * @throws RuntimeException when no replica is defined for the given key
   */
  public MemcachedNode getReplica(String key, int index) {
    TotalConfig totConfig = fullConfig.get();
    Config config = totConfig.getConfig();
    Map nodesMap = totConfig.getNodesMap();
    int vbucket = config.getVbucketByKey(key);
    int serverNumber = config.getReplica(vbucket, index);

    if(serverNumber == -1) {
      getLogger().warn("The key " + key + " pointed to vbucket "
        + vbucket + ", for which no server is responsible in the cluster map."
        + "This can be an indication that either no replica is defined for a "
        + "failed server or more nodes have been failed over than replicas "
        + "defined.");
      return null;
    }

    String server = config.getServer(serverNumber);
    MemcachedNode pNode = nodesMap.get(server);
    return pNode;
  }

  public MemcachedNode getServerByIndex(int k) {
    TotalConfig totConfig = fullConfig.get();
    Config config = totConfig.getConfig();
    Map nodesMap = totConfig.getNodesMap();

    String server = config.getServer(k);
    // choose appropriate MemcachedNode according to config data
    return nodesMap.get(server);
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public Iterator getSequence(String k) {
    return new NullIterator();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Collection getAll() {
    Map nodesMap = fullConfig.get().getNodesMap();
    return nodesMap.values();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public NodeLocator getReadonlyCopy() {
    return this;
  }

  @Override
  public void updateLocator(List nodes) {
    throw new UnsupportedOperationException("Must be updated with a config");
  }

  public void updateLocator(final Collection nodes,
      final Config newconf) {
    Config current = fullConfig.get().getConfig();

    ConfigDifference compareTo = current.compareTo(newconf);

    if (compareTo.isSequenceChanged() || compareTo.getVbucketsChanges() > 0
      || current.getCouchServers().size() != newconf.getCouchServers().size()) {
      getLogger().debug("Updating configuration, received updated configuration"
        + " with significant changes.");
      fullConfig.set(new TotalConfig(newconf,
              fillNodesEntries(newconf, nodes)));
    } else {
      getLogger().debug("Received updated configuration with insignificant "
        + "changes.");
    }
  }

  /**
   * Returns a vbucket index for the given key.
   *
   * @param key the key
   * @return vbucket index
   */
  public int getVBucketIndex(String key) {
    Config config = fullConfig.get().getConfig();
    return config.getVbucketByKey(key);
  }

  private Map fillNodesEntries(
      Config newConfig, final Collection nodes) {
    HashMap vbnodesMap =
        new HashMap();
    getLogger().debug("Updating nodesMap in VBucketNodeLocator.");
    for (String server : newConfig.getServers()) {
      vbnodesMap.put(server, null);
    }

    for (MemcachedNode node : nodes) {
      InetSocketAddress addr = (InetSocketAddress) node.getSocketAddress();
      String address = addr.getAddress().getHostName() + ":" + addr.getPort();
      String hostname = addr.getAddress().getHostAddress() + ":"
        + addr.getPort();

      if (vbnodesMap.containsKey(address)) {
        vbnodesMap.put(address, node);
        getLogger().debug("Adding node with address %s.",
          address);
        getLogger().debug("Node added is %s.", node);
      } else if (vbnodesMap.containsKey(hostname)) {
        vbnodesMap.put(hostname, node);
        getLogger().debug("Adding node with hostname %s.",
          hostname);
        getLogger().debug("Node added is %s.", node);
      }
    }

    // Iterate over the map and check for entries not populated
    Iterator> iterator =
      vbnodesMap.entrySet().iterator();
    while (iterator.hasNext()) {
      Map.Entry entry = iterator.next();
      if (entry.getValue() == null) {
        getLogger().error("Critical reconfiguration error: "
            + "Server list from Configuration and Nodes "
            + "are out of synch. causing %s to be removed",
          entry.getKey());
        iterator.remove();
      }
    }

    return Collections.unmodifiableMap(vbnodesMap);
  }

  /**
   * Method returns the node that is not contained in the specified collection
   * of the failed nodes.
   *
   * @param k the key
   * @param notMyVbucketNodes a collection of the nodes are excluded
   * @return The first MemcachedNode which meets requirements
   */
  public MemcachedNode getAlternative(String k,
      Collection notMyVbucketNodes) {
    // it's safe to only copy the map here, only removing references found to be
    // incorrect, and trying remaining
    Map nodesMap =
        new HashMap(fullConfig.get().getNodesMap());
    Collection nodes = nodesMap.values();
    nodes.removeAll(notMyVbucketNodes);
    if (nodes.isEmpty()) {
      return null;
    } else {
      return nodes.iterator().next();
    }
  }

  /**
   * Gets a list of vBucket indexes for the current replicas defined.
   *
   * This method helps in identifying the actual current replicas because
   * during a rebalance or failover scenario this list can differ from the
   * actual config setting in the bucket.
   *
   * @param key the key to check for.
   * @return a list of currently usable replica indexes.
   */
  public List getReplicaIndexes(String key) {
    TotalConfig totConfig = fullConfig.get();
    Config config = totConfig.getConfig();
    int vbucket = config.getVbucketByKey(key);
    VBucket bucket = config.getVbuckets().get(vbucket);
    List indexes = new ArrayList();
    for (int i = 0; i < VBucket.MAX_REPLICAS; i++) {
      if (bucket.getReplica(i) != VBucket.REPLICA_NOT_USED) {
        indexes.add(i);
      }
    }
    return indexes;
  }

  /**
   * Checks if in the current vbucket the master is not "-1".
   *
   * @param key the key to check for.
   * @return true if there is a master assigned for the key.
   */
  public boolean hasActiveMaster(String key) {
    TotalConfig totConfig = fullConfig.get();
    Config config = totConfig.getConfig();
    int vbucket = config.getVbucketByKey(key);
    VBucket bucket = config.getVbuckets().get(vbucket);
    return bucket.getMaster() != -1;
  }

  private static class TotalConfig {
    private final Config config;
    private final Map nodesMap;

    public TotalConfig(Config newConfig, Map newMap) {
      config = newConfig;
      nodesMap = Collections.unmodifiableMap(newMap);
    }

    protected Config getConfig() {
      return config;
    }

    protected Map getNodesMap() {
      return nodesMap;
    }
  }

  private static class NullIterator implements Iterator {

    public boolean hasNext() {
      return false;
    }

    public MemcachedNode next() {
      throw new NoSuchElementException(
          "VBucketNodeLocators have no alternate nodes.");
    }

    public void remove() {
      throw new UnsupportedOperationException(
          "VBucketNodeLocators have no alternate nodes; cannot remove.");
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy