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

com.couchbase.client.core.node.RoundRobinLocator Maven / Gradle / Ivy

There is a newer version: 2.7.0
Show newest version
/*
 * Copyright (c) 2018 Couchbase, 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.couchbase.client.core.node;

import com.couchbase.client.core.CoreContext;
import com.couchbase.client.core.cnc.events.node.NodeLocatorBugIdentifiedEvent;
import com.couchbase.client.core.config.BucketConfig;
import com.couchbase.client.core.config.ClusterConfig;
import com.couchbase.client.core.config.NodeInfo;
import com.couchbase.client.core.config.PortInfo;
import com.couchbase.client.core.error.FeatureNotAvailableException;
import com.couchbase.client.core.error.ServiceNotAvailableException;
import com.couchbase.client.core.error.context.GenericRequestErrorContext;
import com.couchbase.client.core.msg.CancellationReason;
import com.couchbase.client.core.msg.Request;
import com.couchbase.client.core.msg.Response;
import com.couchbase.client.core.retry.RetryOrchestrator;
import com.couchbase.client.core.retry.RetryReason;
import com.couchbase.client.core.service.ServiceType;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;

import static java.util.Objects.requireNonNull;

/**
 * A {@link Locator} which implements node selection on a round-robin basis.
 *
 * 

This locator simply tracks a counter that gets incremented and applied to the list of * nodes provided. The list of nodes is filtered to make sure that only nodes with the specific * service are taken into account.

* * @since 1.0.0 */ public class RoundRobinLocator implements Locator { /** * Holds the counter which increments for proper round-robin. */ private final AtomicLong counter; /** * Holds the configured service type for this locator. */ private final ServiceType serviceType; public RoundRobinLocator(final ServiceType serviceType) { this(serviceType, new Random().nextInt(1024)); } RoundRobinLocator(final ServiceType serviceType, final long initialValue) { counter = new AtomicLong(initialValue); this.serviceType = serviceType; } @Override public void dispatch(final Request request, final List nodes, final ClusterConfig config, final CoreContext ctx) { if (!checkServiceNotAvailable(request, config)) { return; } boolean isTargeted = request.target() != null; if (!isTargeted && !config.hasClusterOrBucketConfig()) { boolean globalLoadInProgress = ctx.core().configurationProvider().globalConfigLoadInProgress(); boolean bucketLoadInProgress =ctx.core().configurationProvider().bucketConfigLoadInProgress(); boolean loadInProgress = globalLoadInProgress || bucketLoadInProgress; if (loadInProgress) { RetryOrchestrator.maybeRetry( ctx, request, bucketLoadInProgress ? RetryReason.BUCKET_OPEN_IN_PROGRESS : RetryReason.GLOBAL_CONFIG_LOAD_IN_PROGRESS ); } else { request.fail(FeatureNotAvailableException.clusterLevelQuery(serviceType)); } return; } List filteredNodes = filterNodes(nodes, request, config); if (filteredNodes.isEmpty()) { if (serviceShowsUpInConfig(config)) { // No node has the service enabled, but it shows up in the config. This is a race condition, // likely the node/service is currently in progress of being configured, so let's send it into // retry and wait until it can be dispatched. RetryOrchestrator.maybeRetry(ctx, request, RetryReason.NODE_NOT_AVAILABLE); } else { // No node in the cluster has the service enabled and it does not show up in the bucket config, // so we need to cancel the request with a service not available exception. Either the cluster is // not configured with the service in the first place or a failover happened and suddenly there // is no node in the cluster anymore which can serve the request. In any case sending it into // retry will not help resolve the situation so let's make it clear in the exception what's //going on. request.fail(new ServiceNotAvailableException( "The " + request.serviceType().ident() + " service is not available in the cluster.", new GenericRequestErrorContext(request) )); } return; } if (isTargeted) { dispatchTargeted(request, filteredNodes, ctx); } else { dispatchUntargeted(request, filteredNodes, ctx); } } /** * Helper method to check if a given services shows up in a config on the cluster. * * @param clusterConfig the config to check against. * @return true if it shows up at least once, false otherwise. */ private boolean serviceShowsUpInConfig(final ClusterConfig clusterConfig) { if (clusterConfig.globalConfig() != null) { for (PortInfo portInfo : clusterConfig.globalConfig().portInfos()) { if (portInfo.ports().containsKey(serviceType)) { return true; } } } for (BucketConfig bucketConfig : clusterConfig.bucketConfigs().values()) { for (NodeInfo nodeInfo : bucketConfig.nodes()) { if (nodeInfo.services().containsKey(serviceType)) { return true; } } } return false; } /** * Can be overridden to check if a request should be cancelled immediately that the service is not * supported. *

* If this method returns false, something MUST be done with the request or it will time out! */ protected boolean checkServiceNotAvailable(final Request request, final ClusterConfig config) { return true; } private void dispatchTargeted(final Request request, final List nodes, final CoreContext ctx) { for (Node n : nodes) { if (n.identifier().equals(request.target())) { n.send(request); return; } } handleTargetNotAvailable(request, nodes, ctx); } /** * When a targeted request cannot be dispatched, apply some more logic to figure out what to do with it. *

* In the specific case there are two situations that can happen: either the target is there, it's just not ready * to serve requests (yet) or it is not even part of the node list anymore. In the latter case there is no way * the request is going to make progress, so cancel it and give the caller a chance to fetch a new target and * send a new request. * * @param request the request to check. * @param nodes the nodes list to check against. * @param ctx the core context. */ private static void handleTargetNotAvailable(final Request request, final List nodes, final CoreContext ctx) { NodeIdentifier target = requireNonNull(request.target()); for (Node node : nodes) { if (target.equals(node.identifier())) { RetryOrchestrator.maybeRetry(ctx, request, RetryReason.NODE_NOT_AVAILABLE); return; } } request.cancel(CancellationReason.TARGET_NODE_REMOVED); } private void dispatchUntargeted(final Request request, final List nodes, final CoreContext ctx) { int nodeSize = nodes.size(); int offset = (int) Math.floorMod(counter.getAndIncrement(), (long) nodeSize); Node node = nodes.get(offset); if (node != null) { node.send(request); } else { RetryOrchestrator.maybeRetry(ctx, request, RetryReason.NODE_NOT_AVAILABLE); ctx.environment().eventBus().publish(new NodeLocatorBugIdentifiedEvent(ctx)); } } /** * Filters the list of nodes by the {@link ServiceType}. * * @param allNodes all nodes as a source. * @param request the request in scope. * @param config the cluster-level config. * @return the list of filtered nodes. */ private List filterNodes(final List allNodes, final Request request, final ClusterConfig config) { List result = new ArrayList<>(allNodes.size()); for (Node n : allNodes) { if (n.serviceEnabled(serviceType) && nodeCanBeUsed(n, request, config)) { result.add(n); } } return result; } /** * This method can be overridden for additional per-node checks in addition to the service-type * based check already performed in {@link #filterNodes(List, Request, ClusterConfig)}. * *

This method will be called for each node in the list to find out if it can be used in principle * for dispatching the request.

* * @param node the node to check against. * @param request the request in scope. * @param config the cluster-level config. * @return true if it can be used. */ protected boolean nodeCanBeUsed(final Node node, final Request request, final ClusterConfig config) { return true; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy