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

org.apache.camel.component.zookeeper.policy.ZooKeeperRoutePolicy Maven / Gradle / Ivy

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.camel.component.zookeeper.policy;

import java.net.InetAddress;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.String.format;

import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Processor;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.Route;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.zookeeper.SequenceComparator;
import org.apache.camel.component.zookeeper.ZooKeeperEndpoint;
import org.apache.camel.component.zookeeper.ZooKeeperMessage;
import org.apache.camel.impl.JavaUuidGenerator;
import org.apache.camel.impl.RoutePolicySupport;
import org.apache.camel.spi.UuidGenerator;
import org.apache.camel.util.ExchangeHelper;
import org.apache.zookeeper.CreateMode;

/**
 * ZooKeeperRoutePolicy uses the leader election capabilities of a
 * ZooKeeper cluster to control how routes are enabled. It is typically used in
 * fail-over scenarios controlling identical instances of a route across a
 * cluster of Camel based servers.
 * 

* The policy is configured with a 'top n' number of routes that should be * allowed to start, for a master/slave scenario this would be 1. Each instance * of the policy will execute the election algorithm to obtain its position in * the hierarchy of servers, if it is within the 'top n' servers then the policy * is enabled and exchanges can be processed by the route. If not it waits for a * change in the leader hierarchy and then reruns this scenario to see if it is * now in the top n. *

* All instances of the policy must also be configured with the same path on the * ZooKeeper cluster where the election will be carried out. It is good practice * for this to indicate the application e.g. /someapplication/someroute/ note * that these nodes should exist before using the policy. *

* See * for more on how Leader election is archived with ZooKeeper. */ public class ZooKeeperRoutePolicy extends RoutePolicySupport { private String uri; private int enabledCount; private String candidateName; private final Lock lock = new ReentrantLock(); private final CountDownLatch electionComplete = new CountDownLatch(1); private Set suspendedRoutes = new CopyOnWriteArraySet(); private AtomicBoolean shouldProcessExchanges = new AtomicBoolean(); private ProducerTemplate template; private boolean shouldStopConsumer = true; private UuidGenerator uuidGenerator = new JavaUuidGenerator(); private boolean isCandidateCreated; public ZooKeeperRoutePolicy(String uri, int enabledCount) throws Exception { this.uri = uri; this.enabledCount = enabledCount; createCandidateName(); } private void createCandidateName() throws Exception { /** UUID would be enough, also using hostname for human readability */ StringBuilder b = new StringBuilder(InetAddress.getLocalHost().getCanonicalHostName()); b.append("-").append(uuidGenerator.generateUuid()); this.candidateName = b.toString(); } @Override public void onExchangeBegin(Route route, Exchange exchange) { testAndCreateCandidateNode(route); awaitElectionResults(); if (!shouldProcessExchanges.get()) { if (shouldStopConsumer) { stopConsumer(route); } IllegalStateException e = new IllegalStateException("Zookeeper based route policy prohibits processing exchanges, stopping route and failing the exchange"); exchange.setException(e); } else { if (shouldStopConsumer) { startConsumer(route); } } } private void testAndCreateCandidateNode(Route route) { try { lock.lock(); if (!isCandidateCreated) { createCandidateNode(route.getRouteContext().getCamelContext()); isCandidateCreated = true; } } catch (Exception e) { handleException(e); } finally { lock.unlock(); } } private void awaitElectionResults() { while (electionComplete.getCount() > 0) { try { electionComplete.await(); } catch (InterruptedException e1) { } } } private void startConsumer(Route route) { try { lock.lock(); if (suspendedRoutes.contains(route)) { startConsumer(route.getConsumer()); suspendedRoutes.remove(route); } } catch (Exception e) { handleException(e); } finally { lock.unlock(); } } private void stopConsumer(Route route) { try { lock.lock(); // check that we should still suspend once the lock is acquired if (!suspendedRoutes.contains(route) && !shouldProcessExchanges.get()) { stopConsumer(route.getConsumer()); suspendedRoutes.add(route); } } catch (Exception e) { handleException(e); } finally { lock.unlock(); } } private void startAllStoppedConsumers() { try { lock.lock(); if (!suspendedRoutes.isEmpty()) { if (log.isDebugEnabled()) { log.debug(format("'%d' have been stopped previously by poilcy, restarting.", suspendedRoutes.size())); } for (Route suspended : suspendedRoutes) { startConsumer(suspended.getConsumer()); } suspendedRoutes.clear(); } } catch (Exception e) { handleException(e); } finally { lock.unlock(); } } public boolean isShouldStopConsumer() { return shouldStopConsumer; } public void setShouldStopConsumer(boolean shouldStopConsumer) { this.shouldStopConsumer = shouldStopConsumer; } private ZooKeeperEndpoint createCandidateNode(CamelContext camelContext) { this.template = camelContext.createProducerTemplate(); if (log.isInfoEnabled()) { log.info(format("Initializing ZookeeperRoutePolicy with uri '%s'", uri)); } ZooKeeperEndpoint zep = camelContext.getEndpoint(uri, ZooKeeperEndpoint.class); zep.getConfiguration().setCreate(true); String fullpath = createFullPathToCandidate(zep); Exchange e = zep.createExchange(); e.setPattern(ExchangePattern.InOut); e.getIn().setHeader(ZooKeeperMessage.ZOOKEEPER_NODE, fullpath); e.getIn().setHeader(ZooKeeperMessage.ZOOKEEPER_CREATE_MODE, CreateMode.EPHEMERAL_SEQUENTIAL); template.send(zep, e); if (e.isFailed()) { log.error("Error setting up election node " + fullpath, e.getException()); } else { if (log.isInfoEnabled()) { log.info(format("Candidate node '%s' has been created", fullpath)); } try { if (zep != null) { camelContext.addRoutes(new ElectoralMonitorRoute(zep)); } } catch (Exception ex) { log.error("Error configuring ZookeeperRoutePolicy", ex); } } return zep; } private String createFullPathToCandidate(ZooKeeperEndpoint zep) { String fullpath = zep.getConfiguration().getPath(); if (!fullpath.endsWith("/")) { fullpath += "/"; } fullpath += candidateName; return fullpath; } private class ElectoralMonitorRoute extends RouteBuilder { private SequenceComparator comparator = new SequenceComparator(); private ZooKeeperEndpoint zep; public ElectoralMonitorRoute(ZooKeeperEndpoint zep) { this.zep = zep; zep.getConfiguration().setListChildren(true); zep.getConfiguration().setRepeat(true); } @Override public void configure() throws Exception { /** * TODO: this is cheap cheerful but suboptimal; it suffers from the * 'herd effect' that on any change to the candidates list every * policy instance will ask for the entire candidate list again. * This is fine for small numbers of nodes (for scenarios * like Master-Slave it is perfect) but could get noisy if * large numbers of nodes were involved. *

* Better would be to find the position of this node in the list and * watch the node in the position ahead node ahead of this and only * request the candidate list when its status changes. This will * require enhancing the consumer to allow custom operation lists. */ from(zep).sort(body(), comparator).process(new Processor() { @SuppressWarnings("unchecked") public void process(Exchange e) throws Exception { List candidates = (List)ExchangeHelper.getMandatoryInBody(e); int location = Math.abs(Collections.binarySearch(candidates, candidateName)); /** * check if the item at this location starts with this nodes * candidate name */ if (isOurCandidateAtLocationInCandidatesList(candidates, location)) { shouldProcessExchanges.set(location <= enabledCount); if (log.isDebugEnabled()) { log.debug(format("This node is number '%d' on the candidate list, route is configured for the top '%d'. Exchange processing will be %s", location, enabledCount, shouldProcessExchanges.get() ? "enabled" : "disabled")); } startAllStoppedConsumers(); } electionComplete.countDown(); } private boolean isOurCandidateAtLocationInCandidatesList(List candidates, int location) { return location <= candidates.size() && candidates.get(location - 1).startsWith(candidateName); } }); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy