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

com.datastax.driver.core.policies.DCAwareRoundRobinPolicy Maven / Gradle / Ivy

The newest version!
/*
 * Copyright DataStax, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.datastax.driver.core.policies;

import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Configuration;
import com.datastax.driver.core.ConsistencyLevel;
import com.datastax.driver.core.Host;
import com.datastax.driver.core.HostDistance;
import com.datastax.driver.core.Statement;
import com.datastax.driver.$internal.com.google.common.annotations.VisibleForTesting;
import com.datastax.driver.$internal.com.google.common.base.Joiner;
import com.datastax.driver.$internal.com.google.common.base.Preconditions;
import com.datastax.driver.$internal.com.google.common.base.Strings;
import com.datastax.driver.$internal.com.google.common.collect.AbstractIterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A data-center aware Round-robin load balancing policy.
 *
 * 

This policy provides round-robin queries over the node of the local data center. It also * includes in the query plans returned a configurable number of hosts in the remote data centers, * but those are always tried after the local nodes. In other words, this policy guarantees that no * host in a remote data center will be queried unless no host in the local data center can be * reached. * *

If used with a single data center, this policy is equivalent to the {@link RoundRobinPolicy}, * but its DC awareness incurs a slight overhead so the latter should be preferred to this policy in * that case. */ public class DCAwareRoundRobinPolicy implements LoadBalancingPolicy { private static final Logger logger = LoggerFactory.getLogger(DCAwareRoundRobinPolicy.class); /** * Returns a builder to create a new instance. * * @return the builder. */ public static Builder builder() { return new Builder(); } private static final String UNSET = ""; private final ConcurrentMap> perDcLiveHosts = new ConcurrentHashMap>(); private final AtomicInteger index = new AtomicInteger(); @VisibleForTesting volatile String localDc; private final int usedHostsPerRemoteDc; private final boolean dontHopForLocalCL; private volatile Configuration configuration; private DCAwareRoundRobinPolicy( String localDc, int usedHostsPerRemoteDc, boolean allowRemoteDCsForLocalConsistencyLevel, boolean allowEmptyLocalDc) { if (!allowEmptyLocalDc && Strings.isNullOrEmpty(localDc)) throw new IllegalArgumentException("Null or empty data center specified for DC-aware policy"); this.localDc = localDc == null ? UNSET : localDc; this.usedHostsPerRemoteDc = usedHostsPerRemoteDc; this.dontHopForLocalCL = !allowRemoteDCsForLocalConsistencyLevel; } @Override public void init(Cluster cluster, Collection hosts) { if (localDc != UNSET) logger.info("Using provided data-center name '{}' for DCAwareRoundRobinPolicy", localDc); this.configuration = cluster.getConfiguration(); ArrayList notInLocalDC = new ArrayList(); for (Host host : hosts) { String dc = dc(host); // If the localDC was in "auto-discover" mode and it's the first host for which we have a DC, // use it. if (localDc == UNSET && dc != UNSET) { logger.info( "Using data-center name '{}' for DCAwareRoundRobinPolicy (if this is incorrect, please provide the correct datacenter name with DCAwareRoundRobinPolicy constructor)", dc); localDc = dc; } else if (!dc.equals(localDc)) notInLocalDC.add(String.format("%s (%s)", host.toString(), dc)); CopyOnWriteArrayList prev = perDcLiveHosts.get(dc); if (prev == null) perDcLiveHosts.put(dc, new CopyOnWriteArrayList(Collections.singletonList(host))); else prev.addIfAbsent(host); } if (notInLocalDC.size() > 0) { String nonLocalHosts = Joiner.on(",").join(notInLocalDC); logger.warn( "Some contact points don't match local data center. Local DC = {}. Non-conforming contact points: {}", localDc, nonLocalHosts); } this.index.set(new Random().nextInt(Math.max(hosts.size(), 1))); } private String dc(Host host) { String dc = host.getDatacenter(); return dc == null ? localDc : dc; } @SuppressWarnings("unchecked") private static CopyOnWriteArrayList cloneList(CopyOnWriteArrayList list) { return (CopyOnWriteArrayList) list.clone(); } /** * Return the HostDistance for the provided host. * *

This policy consider nodes in the local datacenter as {@code LOCAL}. For each remote * datacenter, it considers a configurable number of hosts as {@code REMOTE} and the rest is * {@code IGNORED}. * *

To configure how many hosts in each remote datacenter should be considered, see {@link * Builder#withUsedHostsPerRemoteDc(int)}. * * @param host the host of which to return the distance of. * @return the HostDistance to {@code host}. */ @Override public HostDistance distance(Host host) { String dc = dc(host); if (dc == UNSET || dc.equals(localDc)) return HostDistance.LOCAL; CopyOnWriteArrayList dcHosts = perDcLiveHosts.get(dc); if (dcHosts == null || usedHostsPerRemoteDc == 0) return HostDistance.IGNORED; // We need to clone, otherwise our subList call is not thread safe dcHosts = cloneList(dcHosts); return dcHosts.subList(0, Math.min(dcHosts.size(), usedHostsPerRemoteDc)).contains(host) ? HostDistance.REMOTE : HostDistance.IGNORED; } /** * Returns the hosts to use for a new query. * *

The returned plan will always try each known host in the local datacenter first, and then, * if none of the local host is reachable, will try up to a configurable number of other host per * remote datacenter. The order of the local node in the returned query plan will follow a * Round-robin algorithm. * * @param loggedKeyspace the keyspace currently logged in on for this query. * @param statement the query for which to build the plan. * @return a new query plan, i.e. an iterator indicating which host to try first for querying, * which one to use as failover, etc... */ @Override public Iterator newQueryPlan(String loggedKeyspace, final Statement statement) { CopyOnWriteArrayList localLiveHosts = perDcLiveHosts.get(localDc); final List hosts = localLiveHosts == null ? Collections.emptyList() : cloneList(localLiveHosts); final int startIdx = index.getAndIncrement(); return new AbstractIterator() { private int idx = startIdx; private int remainingLocal = hosts.size(); // For remote Dcs private Iterator remoteDcs; private List currentDcHosts; private int currentDcRemaining; @Override protected Host computeNext() { while (true) { if (remainingLocal > 0) { remainingLocal--; int c = idx++ % hosts.size(); if (c < 0) { c += hosts.size(); } return hosts.get(c); } if (currentDcHosts != null && currentDcRemaining > 0) { currentDcRemaining--; int c = idx++ % currentDcHosts.size(); if (c < 0) { c += currentDcHosts.size(); } return currentDcHosts.get(c); } ConsistencyLevel cl = statement.getConsistencyLevel() == null ? configuration.getQueryOptions().getConsistencyLevel() : statement.getConsistencyLevel(); if (dontHopForLocalCL && cl.isDCLocal()) return endOfData(); if (remoteDcs == null) { Set copy = new HashSet(perDcLiveHosts.keySet()); copy.remove(localDc); remoteDcs = copy.iterator(); } if (!remoteDcs.hasNext()) break; String nextRemoteDc = remoteDcs.next(); CopyOnWriteArrayList nextDcHosts = perDcLiveHosts.get(nextRemoteDc); if (nextDcHosts != null) { // Clone for thread safety List dcHosts = cloneList(nextDcHosts); currentDcHosts = dcHosts.subList(0, Math.min(dcHosts.size(), usedHostsPerRemoteDc)); currentDcRemaining = currentDcHosts.size(); } } return endOfData(); } }; } @Override public void onUp(Host host) { String dc = dc(host); // If the localDC was in "auto-discover" mode and it's the first host for which we have a DC, // use it. if (localDc == UNSET && dc != UNSET) { logger.info( "Using data-center name '{}' for DCAwareRoundRobinPolicy (if this is incorrect, please provide the correct datacenter name with DCAwareRoundRobinPolicy constructor)", dc); localDc = dc; } CopyOnWriteArrayList dcHosts = perDcLiveHosts.get(dc); if (dcHosts == null) { CopyOnWriteArrayList newMap = new CopyOnWriteArrayList(Collections.singletonList(host)); dcHosts = perDcLiveHosts.putIfAbsent(dc, newMap); // If we've successfully put our new host, we're good, otherwise we've been beaten so continue if (dcHosts == null) return; } dcHosts.addIfAbsent(host); } @Override public void onDown(Host host) { CopyOnWriteArrayList dcHosts = perDcLiveHosts.get(dc(host)); if (dcHosts != null) dcHosts.remove(host); } @Override public void onAdd(Host host) { onUp(host); } @Override public void onRemove(Host host) { onDown(host); } @Override public void close() { // nothing to do } /** Helper class to build the policy. */ public static class Builder { private String localDc; private int usedHostsPerRemoteDc; private boolean allowRemoteDCsForLocalConsistencyLevel; /** * Sets the name of the datacenter that will be considered "local" by the policy. * *

This must be the name as known by Cassandra (in other words, the name in that appears in * {@code system.peers}, or in the output of admin tools like nodetool). * *

If this method isn't called, the policy will default to the datacenter of the first node * connected to. This will always be ok if all the contact points use at {@code Cluster} * creation are in the local data-center. Otherwise, you should provide the name yourself with * this method. * * @param localDc the name of the datacenter. It should not be {@code null}. * @return this builder. */ public Builder withLocalDc(String localDc) { Preconditions.checkArgument( !Strings.isNullOrEmpty(localDc), "localDc name can't be null or empty. If you want to let the policy autodetect the datacenter, don't call Builder.withLocalDC"); this.localDc = localDc; return this; } /** * Sets the number of hosts per remote datacenter that the policy should consider. * *

The policy's {@code distance()} method will return a {@code HostDistance.REMOTE} distance * for only {@code usedHostsPerRemoteDc} hosts per remote datacenter. Other hosts of the remote * datacenters will be ignored (and thus no connections to them will be maintained). * *

If {@code usedHostsPerRemoteDc > 0}, then if for a query no host in the local datacenter * can be reached and if the consistency level of the query is not {@code LOCAL_ONE} or {@code * LOCAL_QUORUM}, then up to {@code usedHostsPerRemoteDc} hosts per remote datacenter will be * tried by the policy as a fallback. By default, no remote host will be used for {@code * LOCAL_ONE} and {@code LOCAL_QUORUM}, since this would change the meaning of the consistency * level, somewhat breaking the consistency contract (this can be overridden with {@link * #allowRemoteDCsForLocalConsistencyLevel()}). * *

If this method isn't called, the policy will default to 0. * * @param usedHostsPerRemoteDc the number. * @return this builder. */ public Builder withUsedHostsPerRemoteDc(int usedHostsPerRemoteDc) { Preconditions.checkArgument( usedHostsPerRemoteDc >= 0, "usedHostsPerRemoteDc must be equal or greater than 0"); this.usedHostsPerRemoteDc = usedHostsPerRemoteDc; return this; } /** * Allows the policy to return remote hosts when building query plans for queries having * consistency level {@code LOCAL_ONE} or {@code LOCAL_QUORUM}. * *

When used in conjunction with {@link #withUsedHostsPerRemoteDc(int) usedHostsPerRemoteDc} * > 0, this overrides the policy of never using remote datacenter nodes for {@code LOCAL_ONE} * and {@code LOCAL_QUORUM} queries. It is however inadvisable to do so in almost all cases, as * this would potentially break consistency guarantees and if you are fine with that, it's * probably better to use a weaker consistency like {@code ONE}, {@code TWO} or {@code THREE}. * As such, this method should generally be avoided; use it only if you know and understand what * you do. * * @return this builder. */ public Builder allowRemoteDCsForLocalConsistencyLevel() { this.allowRemoteDCsForLocalConsistencyLevel = true; return this; } /** * Builds the policy configured by this builder. * * @return the policy. */ public DCAwareRoundRobinPolicy build() { if (usedHostsPerRemoteDc == 0 && allowRemoteDCsForLocalConsistencyLevel) { logger.warn( "Setting allowRemoteDCsForLocalConsistencyLevel has no effect if usedHostsPerRemoteDc = 0. " + "This setting will be ignored"); } return new DCAwareRoundRobinPolicy( localDc, usedHostsPerRemoteDc, allowRemoteDCsForLocalConsistencyLevel, true); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy