PageRenderTime 253ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/policy/ZooKeeperElection.java

https://gitlab.com/matticala/apache-camel
Java | 255 lines | 172 code | 29 blank | 54 comment | 11 complexity | 68540e85989e1cdb9ef3a5b805cb180f MD5 | raw file
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package org.apache.camel.component.zookeeper.policy;
  18. import java.net.InetAddress;
  19. import java.net.UnknownHostException;
  20. import java.util.ArrayList;
  21. import java.util.List;
  22. import java.util.concurrent.CountDownLatch;
  23. import java.util.concurrent.atomic.AtomicBoolean;
  24. import java.util.concurrent.locks.Lock;
  25. import java.util.concurrent.locks.ReentrantLock;
  26. import org.apache.camel.CamelContext;
  27. import org.apache.camel.Exchange;
  28. import org.apache.camel.ExchangePattern;
  29. import org.apache.camel.Processor;
  30. import org.apache.camel.ProducerTemplate;
  31. import org.apache.camel.builder.RouteBuilder;
  32. import org.apache.camel.component.zookeeper.SequenceComparator;
  33. import org.apache.camel.component.zookeeper.ZooKeeperEndpoint;
  34. import org.apache.camel.component.zookeeper.ZooKeeperMessage;
  35. import org.apache.camel.impl.JavaUuidGenerator;
  36. import org.apache.camel.spi.UuidGenerator;
  37. import org.apache.zookeeper.CreateMode;
  38. import org.slf4j.Logger;
  39. import org.slf4j.LoggerFactory;
  40. /**
  41. * <code>ZooKeeperElection</code> uses the leader election capabilities of a
  42. * ZooKeeper cluster to control which nodes are enabled. It is typically used in
  43. * fail-over scenarios controlling identical instances of an application across
  44. * a cluster of Camel based servers. <p> The election is configured with a 'top
  45. * n' number of servers that should be marked as master, for a simple
  46. * master/slave scenario this would be 1. Each instance will execute the
  47. * election algorithm to obtain its position in the hierarchy of servers, if it
  48. * is within the 'top n' servers then the node is enabled and isMaster() will
  49. * return 'true'. If not it waits for a change in the leader hierarchy and then
  50. * reruns this scenario to see if it is now in the top n. <p> All instances of
  51. * the election must also be configured with the same path on the ZooKeeper
  52. * cluster where the election will be carried out. It is good practice for this
  53. * to indicate the application e.g. <tt>/someapplication/someroute/</tt> note
  54. * that these nodes should exist before using the election. <p> See <a
  55. * href="http://hadoop.apache.org/zookeeper/docs/current/recipes.html#sc_leaderElection">
  56. * for more on how Leader election</a> is archived with ZooKeeper.
  57. */
  58. public class ZooKeeperElection {
  59. private static final Logger LOG = LoggerFactory.getLogger(ZooKeeperElection.class);
  60. private final ProducerTemplate producerTemplate;
  61. private final CamelContext camelContext;
  62. private final String uri;
  63. private final String candidateName;
  64. private final Lock lock = new ReentrantLock();
  65. private final CountDownLatch electionComplete = new CountDownLatch(1);
  66. private AtomicBoolean masterNode = new AtomicBoolean();
  67. private volatile boolean isCandidateCreated;
  68. private int enabledCount = 1;
  69. private UuidGenerator uuidGenerator = new JavaUuidGenerator();
  70. private final List<ElectionWatcher> watchers = new ArrayList<>();
  71. public ZooKeeperElection(CamelContext camelContext, String uri, int enabledCount) {
  72. this(camelContext.createProducerTemplate(), camelContext, uri, enabledCount);
  73. }
  74. public ZooKeeperElection(ProducerTemplate producerTemplate, CamelContext camelContext, String uri, int enabledCount) {
  75. this.camelContext = camelContext;
  76. this.producerTemplate = producerTemplate;
  77. this.uri = uri;
  78. this.enabledCount = enabledCount;
  79. this.candidateName = createCandidateName();
  80. }
  81. public boolean isMaster() {
  82. if (!isCandidateCreated) {
  83. testAndCreateCandidateNode();
  84. awaitElectionResults();
  85. }
  86. return masterNode.get();
  87. }
  88. private String createCandidateName() {
  89. StringBuilder builder = new StringBuilder();
  90. try {
  91. /* UUID would be enough, also using hostname for human readability */
  92. builder.append(InetAddress.getLocalHost().getCanonicalHostName());
  93. } catch (UnknownHostException ex) {
  94. LOG.warn("Failed to get the local hostname.", ex);
  95. builder.append("unknown-host");
  96. }
  97. builder.append("-").append(uuidGenerator.generateUuid());
  98. return builder.toString();
  99. }
  100. private void testAndCreateCandidateNode() {
  101. try {
  102. lock.lock();
  103. if (!isCandidateCreated) {
  104. createCandidateNode(camelContext);
  105. isCandidateCreated = true;
  106. }
  107. } catch (Exception e) {
  108. handleException(e);
  109. } finally {
  110. lock.unlock();
  111. }
  112. }
  113. private void awaitElectionResults() {
  114. while (electionComplete.getCount() > 0) {
  115. try {
  116. LOG.debug("Awaiting election results...");
  117. electionComplete.await();
  118. } catch (InterruptedException e1) {
  119. // do nothing here
  120. }
  121. }
  122. }
  123. private ZooKeeperEndpoint createCandidateNode(CamelContext camelContext) {
  124. LOG.info("Initializing ZookeeperElection with uri '{}'", uri);
  125. ZooKeeperEndpoint zep = camelContext.getEndpoint(uri, ZooKeeperEndpoint.class);
  126. zep.getConfiguration().setCreate(true);
  127. String fullpath = createFullPathToCandidate(zep);
  128. Exchange e = zep.createExchange();
  129. e.setPattern(ExchangePattern.InOut);
  130. e.getIn().setHeader(ZooKeeperMessage.ZOOKEEPER_NODE, fullpath);
  131. e.getIn().setHeader(ZooKeeperMessage.ZOOKEEPER_CREATE_MODE, CreateMode.EPHEMERAL_SEQUENTIAL);
  132. producerTemplate.send(zep, e);
  133. if (e.isFailed()) {
  134. LOG.warn("Error setting up election node " + fullpath, e.getException());
  135. } else {
  136. LOG.info("Candidate node '{}' has been created", fullpath);
  137. try {
  138. camelContext.addRoutes(new ElectoralMonitorRoute(zep));
  139. } catch (Exception ex) {
  140. LOG.warn("Error configuring ZookeeperElection", ex);
  141. }
  142. }
  143. return zep;
  144. }
  145. private String createFullPathToCandidate(ZooKeeperEndpoint zep) {
  146. String fullpath = zep.getConfiguration().getPath();
  147. if (!fullpath.endsWith("/")) {
  148. fullpath += "/";
  149. }
  150. fullpath += candidateName;
  151. return fullpath;
  152. }
  153. private void handleException(Exception e) {
  154. throw new RuntimeException(e.getMessage(), e);
  155. }
  156. private void notifyElectionWatchers() {
  157. for (ElectionWatcher watcher : watchers) {
  158. try {
  159. watcher.electionResultChanged();
  160. } catch (Exception e) {
  161. LOG.warn("Election watcher " + watcher + " of type " + watcher.getClass() + " threw an exception.", e);
  162. }
  163. }
  164. }
  165. public boolean addElectionWatcher(ElectionWatcher e) {
  166. return watchers.add(e);
  167. }
  168. public boolean removeElectionWatcher(ElectionWatcher o) {
  169. return watchers.remove(o);
  170. }
  171. private class ElectoralMonitorRoute extends RouteBuilder {
  172. private SequenceComparator comparator = new SequenceComparator();
  173. private ZooKeeperEndpoint zep;
  174. ElectoralMonitorRoute(ZooKeeperEndpoint zep) {
  175. this.zep = zep;
  176. zep.getConfiguration().setListChildren(true);
  177. zep.getConfiguration().setSendEmptyMessageOnDelete(true);
  178. zep.getConfiguration().setRepeat(true);
  179. }
  180. @Override
  181. public void configure() throws Exception {
  182. /**
  183. * TODO: this is cheap cheerful but suboptimal; it suffers from the
  184. * 'herd effect' that on any change to the candidates list every
  185. * policy instance will ask for the entire candidate list again.
  186. * This is fine for small numbers of nodes (for scenarios like
  187. * Master-Slave it is perfect) but could get noisy if large numbers
  188. * of nodes were involved. <p> Better would be to find the position
  189. * of this node in the list and watch the node in the position ahead
  190. * node ahead of this and only request the candidate list when its
  191. * status changes. This will require enhancing the consumer to allow
  192. * custom operation lists.
  193. */
  194. from(zep).id("election-route-" + candidateName).sort(body(), comparator).process(new Processor() {
  195. @Override
  196. public void process(Exchange e) throws Exception {
  197. @SuppressWarnings("unchecked")
  198. List<String> candidates = e.getIn().getMandatoryBody(List.class);
  199. // we cannot use the binary search here and the candidates a not sorted in the normal way
  200. /**
  201. * check if the item at this location starts with this nodes
  202. * candidate name
  203. */
  204. int location = findCandidateLocationInCandidatesList(candidates, candidateName);
  205. if (location != -1) {
  206. // set the nodes
  207. masterNode.set(location <= enabledCount);
  208. LOG.debug("This node is number '{}' on the candidate list, election is configured for the top '{}'. this node will be {}",
  209. new Object[]{location, enabledCount, masterNode.get() ? "enabled" : "disabled"}
  210. );
  211. }
  212. electionComplete.countDown();
  213. notifyElectionWatchers();
  214. }
  215. private int findCandidateLocationInCandidatesList(List<String> candidates, String candidateName) {
  216. for (int location = 1; location <= candidates.size(); location++) {
  217. if (candidates.get(location - 1).startsWith(candidateName)) {
  218. return location;
  219. }
  220. }
  221. return -1;
  222. }
  223. });
  224. }
  225. }
  226. }