Java tutorial
/* * @(#)HazelcastHelper.java 2016/10/13 * * Copyright (c) 2016 Joseph S. Kuo * All Rights Reserved. * * --LICENSE NOTICE-- * 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. * --LICENSE NOTICE-- */ package org.cyberjos.jcconf2016.node; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HashSet; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.locks.Lock; import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.stereotype.Component; import com.hazelcast.config.ClasspathXmlConfig; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IAtomicLong; import com.hazelcast.core.IAtomicReference; import com.hazelcast.core.IMap; import com.hazelcast.core.IQueue; import com.hazelcast.core.ITopic; /** * Hazelcast helper. * * @author Joseph S. Kuo * @since 0.0., 2016/10/13 */ @Component public class HazelcastHelper { /** * Data time formatter. */ public static final DateTimeFormatter NAME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss-SSS"); /** * The key to access big data map. */ public static final String BIG_DATA = "BIG_DATA"; /** * The logger. */ static final Logger logger = LogManager.getLogger(HazelcastHelper.class); /** * The key to access all active nodes. */ private static final String ACTIVE_NODES = "ACTIVE_NODES"; /** * The key to access master node. */ private static final String MASTER_NODE = "MASTER_NODE"; /** * The key to access task queue. */ private static final String TASK_QUEUE = "TASK_QUEUE"; /** * Waiting milliseconds for a producer. */ private static int WAIT_PRODUCER = 15000; /** * Minimum waiting milliseconds for a consumer. */ private static int MIN_WAIT_CONSUMER = 10000; /** * Maximum waiting milliseconds for a consumer. */ private static int MAX_WAIT_CONSUMER = 20000; /** * Minimum task size indicated by a producer. */ private static int MIN_SIZE = 10; /** * Maximum task size indicated by a producer. */ private static int MAX_SIZE = 20; /** * Minimum value generated by a consumer. */ private static int MIN_VALUE = 10; /** * Maximum value generated by a consumer. */ private static int MAX_VALUE = 1000 * 1000; /** * The working mode. */ private static final boolean WORKING_MODE = true; /** * Hazelcast instance holder. */ private static Holder hazelcastHolder = Holder.INSTANCE; /** * The {@code HazelcastInstance} holder is a singleton enumeration for the * usage of Hazelcast node. * * @author Joseph S. Kuo * @since 0.0.1, 2016/10/13 */ private enum Holder { /** * The enumeration for singleton instance. */ INSTANCE; /** * The singleton instance */ private final HazelcastInstance hazelcastInstance; /** * Default constructor. */ private Holder() { this.hazelcastInstance = Hazelcast.newHazelcastInstance(new ClasspathXmlConfig("hazelcast.xml")); logger.info("[{}] Hazelcast instance has been launched.", this.getMemberId()); } /** * Returns the singleton instance of {@code HazelcastInstance}. * * @return the singleton instance of {@code HazelcastInstance} */ public HazelcastInstance getInstance() { return this.hazelcastInstance; } /** * Returns the member ID. * * @return the member ID */ public String getMemberId() { return this.hazelcastInstance.getCluster().getLocalMember().getUuid(); } } /** * Registers the given node to the active node set. * * @param cloudNode the node to be registered * @throws NullPointerException if the given node is {@code null} */ public void registerNode(final CloudNode cloudNode) { Objects.requireNonNull(cloudNode, "The given cloud node must not be null."); final String nodeName = cloudNode.getName(); final NodeRecord record = new NodeRecord(nodeName, hazelcastHolder.getMemberId()); HazelcastHelper.getMap(ACTIVE_NODES).put(nodeName, record); HazelcastHelper.<CloudNodeMessage>getTopic(nodeName).addMessageListener(cloudNode); hazelcastHolder.getInstance().getCluster().addMembershipListener(cloudNode); logger.info("[{}] The given node registered: {}", nodeName, record); final Optional<NodeRecord> optional = this.getMasterNodeRecord(); logger.info("[{}] Found the master node: {}", nodeName, optional.orElse(null)); if (!optional.isPresent()) { this.setMaster(cloudNode); return; } logger.info("[{}] Found the master node: {}", nodeName, optional.get()); if (WORKING_MODE) { this.runConsumer(cloudNode); } } /** * Removes the node with the given name from the active node set. * * @param nodeName the node to be removed * @throws NullPointerException if the given node name is {@code null} */ public void unregisterNode(final String nodeName) { Objects.requireNonNull(nodeName, "The given node name must not be null."); final NodeRecord record = HazelcastHelper.<String, NodeRecord>getMap(ACTIVE_NODES).remove(nodeName); HazelcastHelper.getTopic(nodeName).destroy(); hazelcastHolder.getInstance().getCluster().removeMembershipListener(nodeName); logger.info("[{}] The node is un-registered: {}", nodeName, record); } /** * Sets the given node to be the master node. * * @param cloudNode the node to become master * @return {@code true} if the given node becomes the master node * successfully * @throws NullPointerException if the given node is {@code null} */ public synchronized boolean setMaster(final CloudNode cloudNode) { Objects.requireNonNull(cloudNode, "The given cloud node must not be null."); final Lock lock = hazelcastHolder.getInstance().getLock("my-distributed-lock"); lock.lock(); try { final String nodeName = cloudNode.getName(); final Optional<NodeRecord> optional = this.getMasterNodeRecord(); logger.info("[{}] Ensure the master node: {}", nodeName, optional.orElse(null)); if (optional.isPresent()) { final NodeRecord masterRecord = optional.get(); final long count = hazelcastHolder.getInstance().getCluster().getMembers().stream() .filter(member -> StringUtils.equals(masterRecord.getMemberId(), member.getUuid())).count(); if (count != 0) { logger.warn("[{}] The master node has already existed: {}", nodeName, masterRecord); return false; } this.unregisterNode(masterRecord.getNodeName()); } final NodeRecord newMasterRecord = HazelcastHelper.<String, NodeRecord>getMap(ACTIVE_NODES) .get(nodeName); HazelcastHelper.getAtomicReference(MASTER_NODE).set(newMasterRecord); logger.info("[{}] This node becomes the new master node: {}", nodeName, newMasterRecord); if (WORKING_MODE) { this.runProducer(cloudNode); } } finally { lock.unlock(); } return true; } /** * Returns {@code true} if the given cloud node is the master node. * * @param cloudNode the cloud node * @return {@code true} if the given cloud node is the master node * @throws NullPointerException if the given node is {@code null} */ public boolean isMaster(final CloudNode cloudNode) { Objects.requireNonNull(cloudNode, "The given node must not be null."); final Optional<NodeRecord> optional = this.getMasterNodeRecord(); return optional.isPresent() && StringUtils.equals(cloudNode.getName(), optional.get().getNodeName()); } /** * Returns the node record of the master node. * * @return the node record of the master node */ public Optional<NodeRecord> getMasterNodeRecord() { return Optional.ofNullable(HazelcastHelper.<NodeRecord>getAtomicReference(MASTER_NODE).get()); } /** * Sends the given message to the specified node. * * @param nodeMessage the node message * @throws NullPointerException if the given node is {@code null} */ public void send(final CloudNodeMessage nodeMessage) { Objects.requireNonNull(nodeMessage, "The given cloud message must not be null."); HazelcastHelper.getTopic(nodeMessage.getTo()).publish(nodeMessage); } /** * Returns the active node set. * * @return the active node set */ public Set<String> getActiveNodes() { return HazelcastHelper.<String, NodeRecord>getMap(ACTIVE_NODES).keySet(); } /** * Returns the task queue. * * @return the task queue */ public BlockingQueue<Integer> getTaskQueue() { return HazelcastHelper.getQueue(TASK_QUEUE); } /** * Returns the distributed map related with the given name. * * @param <K> the type of key * @param <V> the type of value * @param mapName the distributed map * @return the name of map * @throws NullPointerException if the given name is {@code null} */ private static <K, V> IMap<K, V> getMap(final String mapName) { Objects.requireNonNull(mapName, "The given map name must not be null."); return hazelcastHolder.getInstance().getMap(mapName); } /** * Returns the distributed blocking queue related with the given name. * * @param <T> the type of objects stored in the queue * @param queueName the distributed blocking queue * @return the name of queue * @throws NullPointerException if the given name is {@code null} */ private static <T> IQueue<T> getQueue(final String queueName) { Objects.requireNonNull(queueName, "The given queue name must not be null."); return hazelcastHolder.getInstance().getQueue(queueName); } /** * Returns the topic related with the given name. * * @param <T> the type of objects transmitted by the topic * @param topicName the topic * @return the name of topic * @throws NullPointerException if the given name is {@code null} */ private static <T> ITopic<T> getTopic(final String topicName) { Objects.requireNonNull(topicName, "The given topic name must not be null."); return hazelcastHolder.getInstance().getTopic(topicName); } /** * Returns the atomic reference related with the given name. * * @param <T> the type of objects handled by {@code AtomicReference} * @param referenceName the atomic reference * @return the name of atomic reference * @throws NullPointerException if the given name is {@code null} */ private static <T> IAtomicReference<T> getAtomicReference(final String referenceName) { Objects.requireNonNull(referenceName, "The given atomic reference name must not be null."); return hazelcastHolder.getInstance().getAtomicReference(referenceName); } /** * Returns the atomic long integer related with the given name. * * @param numberName the atomic long integer * @return the name of atomic long integer * @throws NullPointerException if the given name is {@code null} */ public static IAtomicLong getAtomicLong(final String numberName) { Objects.requireNonNull(numberName, "The given atomic integer name must not be null."); return hazelcastHolder.getInstance().getAtomicLong(numberName); } /** * Creates and runs a new consumer. * * @param cloudNode the cloud node which runs this new consumer */ private void runConsumer(final CloudNode cloudNode) { final Thread thread = new Thread(() -> { final long serialNumber = HazelcastHelper.getAtomicLong("WORKER_SN").incrementAndGet(); final String time = LocalDateTime.now().format(NAME_FORMATTER); final String workerName = String.format("Worker-%d-%s", serialNumber, time); final String nodeName = cloudNode.getName(); logger.info("[{}] Starting a new consumer thread: {}.", nodeName, workerName); while (!this.isMaster(cloudNode)) { try { logger.debug("[{}] Waiting for incoming task...", workerName); final int taskSize = HazelcastHelper.this.getTaskQueue().take(); logger.debug("[{}] Received task size: {}", workerName, taskSize); final IMap<Long, Integer> map = HazelcastHelper.getMap(BIG_DATA); final IAtomicLong serial = HazelcastHelper.getAtomicLong(BIG_DATA); logger.debug("[{}] Before map size : {}, SN: {}", workerName, map.size(), serial.get()); for (int i = 0; i < taskSize; i++) { map.put(serial.incrementAndGet(), RandomUtils.nextInt(MIN_VALUE, MAX_VALUE)); } logger.debug("[{}] After map size: {}, SN: {}", workerName, map.size(), serial.get()); logger.info("[{}] Finished task: {}", workerName, taskSize); Thread.sleep(RandomUtils.nextInt(MIN_WAIT_CONSUMER, MAX_WAIT_CONSUMER)); } catch (final Exception ex) { logger.error(String.format("[%s] Exception occurred", workerName), ex); } } }); thread.start(); } /** * Creates and runs a new producer. * * @param cloudNode the cloud node which runs this new producer */ private void runProducer(final CloudNode cloudNode) { final Thread thread = new Thread(() -> { final String nodeName = cloudNode.getName(); logger.info("[{}] Producer thread started.", nodeName); while (this.isMaster(cloudNode)) { try { final Set<String> nodes = new HashSet<>(HazelcastHelper.this.getActiveNodes()); nodes.remove(cloudNode.getName()); for (int i = 0; i < nodes.size(); i++) { final int taskSize = RandomUtils.nextInt(MIN_SIZE, MAX_SIZE); HazelcastHelper.this.getTaskQueue().put(taskSize); logger.info("[{}] Added task size: {}", nodeName, taskSize); } Thread.sleep(WAIT_PRODUCER); } catch (final Exception ex) { logger.error(String.format("[%s] Exception occurred", nodeName), ex); } } }); thread.start(); } }