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

ru.taskurotta.service.hz.queue.HzQueueService Maven / Gradle / Ivy

The newest version!
package ru.taskurotta.service.hz.queue;

import com.hazelcast.core.DistributedObject;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IExecutorService;
import com.hazelcast.core.ILock;
import com.hazelcast.core.IMap;
import com.hazelcast.core.Member;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.taskurotta.hazelcast.queue.CachedQueue;
import ru.taskurotta.hazelcast.queue.delay.CachedDelayQueue;
import ru.taskurotta.hazelcast.queue.delay.QueueFactory;
import ru.taskurotta.hazelcast.util.ClusterUtils;
import ru.taskurotta.service.console.model.GenericPage;
import ru.taskurotta.service.console.model.QueueStatVO;
import ru.taskurotta.service.console.retriever.QueueInfoRetriever;
import ru.taskurotta.service.hz.console.HzQueueStatTask;
import ru.taskurotta.service.queue.QueueService;
import ru.taskurotta.service.queue.TaskQueueItem;
import ru.taskurotta.transport.utils.TransportUtils;
import ru.taskurotta.util.ActorUtils;
import ru.taskurotta.util.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Implementation of a QueueService for a Hazelcast cluster.
 * Date: 06.12.13 15:30
 */
public class HzQueueService implements QueueService, QueueInfoRetriever {

    public static AtomicInteger pushedTaskToQueue = new AtomicInteger();
    public static AtomicInteger pushedTaskToQueueWithDelay = new AtomicInteger();

    private static final Logger logger = LoggerFactory.getLogger(HzQueueService.class);
    private transient final ReentrantLock lock = new ReentrantLock();
    private long pollDelay;
    protected final ConcurrentHashMap lastPolledTaskEnqueueTimes = new ConcurrentHashMap<>();

    protected QueueFactory queueFactory;
    protected HazelcastInstance hazelcastInstance;
    protected String queueNamePrefix;

    protected static final String HZ_QUEUE_INFO_EXECUTOR_SERVICE = "hzQueueInfoExecutorService";

    private static final String LAST_POLLED_TASK_ENQUEUE_TIME = "lastPolledTaskEnqueueTimes";
    private static final String SYNCH_LOCK_NAME = HzQueueService.class.getName().concat("#SINCH_LOCK");
    private static final String DRAIN_LOCK_NAME = HzQueueService.class.getName().concat("#DRAIN_LOCK");

    private ILock synchLock = null;

    private Map> queueMap = new ConcurrentHashMap<>();

    private ILock drainLock;

    private AtomicInteger cnt = new AtomicInteger(0);

    public HzQueueService(QueueFactory queueFactory, HazelcastInstance hazelcastInstance, String queueNamePrefix, long mergePeriodMs, long pollDelay) {
        this.queueFactory = queueFactory;
        this.hazelcastInstance = hazelcastInstance;
        this.queueNamePrefix = queueNamePrefix;
        this.pollDelay = pollDelay;

        this.synchLock = hazelcastInstance.getLock(SYNCH_LOCK_NAME);
        this.drainLock = hazelcastInstance.getLock(DRAIN_LOCK_NAME);

        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

        //Queue statistics for recovery
        scheduledExecutorService.scheduleAtFixedRate(new StatisticsMerger(), 0, mergePeriodMs, TimeUnit.MILLISECONDS);
    }

    private static QueueStatVO getItemByName(List list, String name) {
        QueueStatVO result = null;

        if (list != null && !list.isEmpty() && !StringUtils.isBlank(name)) {
            for (QueueStatVO qs : list) {
                if (name.equals(qs.getName())) {
                    result = qs;
                    break;
                }
            }
        }

        return result;
    }

    class StatisticsMerger implements Runnable {

        @Override
        public void run() {

            synchLock.lock();

            try { //should always wrap ScheduledExecutorService tasks to try-catch to prevent silent task death

                IMap mutualMap = hazelcastInstance.getMap(LAST_POLLED_TASK_ENQUEUE_TIME);
                Map myMap = lastPolledTaskEnqueueTimes;

                Set keys = new HashSet<>();
                keys.addAll(mutualMap.keySet());
                keys.addAll(myMap.keySet());

                for (String key : keys) {
                    Long mutualValue = mutualMap.get(key);
                    Long myValue = myMap.get(key);

                    if (mutualValue == null) {
                        mutualMap.set(key, myValue);

                    } else if (myValue == null) {
                        myMap.put(key, mutualValue);

                    } else if (myValue > mutualValue) {
                        mutualMap.set(key, myValue);

                    } else {
                        myMap.put(key, mutualValue);

                    }
                }

            } catch (Throwable e) {
                logger.error("StatisticsMerger iteration failed", e);
            } finally {
                synchLock.unlock();
            }
        }
    }

    @Override
    public long getLastPolledTaskEnqueueTime(String queueName) {
        Long time = lastPolledTaskEnqueueTimes.get(ActorUtils.toPrefixed(queueName, queueNamePrefix));

        // if no tasks in queue, than return -1
        if (time == null) {
            return -1;
        }

        return time;
    }

    @Override
    public void clearQueue(String queueName) {
        CachedDelayQueue queue = getQueue(ActorUtils.toPrefixed(queueName, queueNamePrefix));
        queue.clear();
    }

    @Override
    public void removeQueue(String queueName) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            CachedDelayQueue queue = queueMap.get(ActorUtils.toPrefixed(queueName, queueNamePrefix));
            logger.debug("Removing queue with name [{}], cached queue is [{}]", queueName, queue);
            if (queue != null) {
                queueMap.remove(queueName);
                queue.destroy();
            }
        } finally {
            lock.unlock();
        }
    }

    @Override
    public long getQueueStorageCount(String queueName) {
        CachedDelayQueue queue = queueMap.get(ActorUtils.toPrefixed(queueName, queueNamePrefix));
        return queue != null ? queue.size() : -1;//-1 indicating that there is no such queue cached yet
    }

    @Override
    public TaskQueueItem poll(String actorId, String taskList) {

        String queueName = createQueueName(actorId, taskList);
        CachedDelayQueue queue = getQueue(queueName);

        TaskQueueItem result = null;
        try {
            result = queue.poll(pollDelay, TimeUnit.MILLISECONDS);
            if (logger.isDebugEnabled()) {
                logger.debug("poll() returns taskQueueItem [{}]. [{}].size: {}", result, queueName, queue.size());
            }

            updateQueueEffectiveTime(queueName, result);

        } catch (InterruptedException e) {
            logger.error("Queue poll operation interrupted", e);
        }

        return result;
    }

    private void updateQueueEffectiveTime(String queueName, TaskQueueItem result) {
        long lastPolledTaskEnqueueTime = (result != null ? result.getEnqueueTime() : System.currentTimeMillis());
        lastPolledTaskEnqueueTimes.put(queueName, lastPolledTaskEnqueueTime);
        logger.debug("lastPolledTaskEnqueueTimes updated for queue[{}] with new value [{}]", queueName, lastPolledTaskEnqueueTime);
    }

    @Override
    public boolean enqueueItem(String actorId, UUID taskId, UUID processId, long startTime, String taskList) {

        pushedTaskToQueue.incrementAndGet();

        long now = System.currentTimeMillis();

        // set it to current time for precisely repeat
        if (startTime <= 0L) {
            startTime = now;
        }

        TaskQueueItem taskQueueItem = new TaskQueueItem();
        taskQueueItem.setTaskId(taskId);
        taskQueueItem.setProcessId(processId);
        taskQueueItem.setStartTime(startTime);
        taskQueueItem.setEnqueueTime(now);
        taskQueueItem.setTaskList(taskList);

        String queueName = createQueueName(actorId, taskList);
        CachedDelayQueue queue = getQueue(queueName);

        long delayTime = startTime - now;

        if (delayTime > 0) {
            pushedTaskToQueueWithDelay.incrementAndGet();
        }

        try {
            return queue.delayOffer(taskQueueItem, delayTime, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    public boolean isTaskInQueue(String actorId, String taskList, UUID taskId, UUID processId) {
        throw new UnsupportedOperationException("Only for all-in-memory backend");
    }

    @Override
    public String createQueueName(String actorId, String taskList) {
        return TransportUtils.createQueueName(actorId, taskList, queueNamePrefix);
    }

    @Override
    public GenericPage getQueueList(int pageNum, int pageSize) {

        List queueNamesList = getTaskQueueNamesByPrefix(queueNamePrefix, null, false);

        logger.debug("Stored queue names for queue service are [{}]", queueNamesList);

        String[] queueNames = queueNamesList.toArray(new String[queueNamesList.size()]);
        List result = new ArrayList<>(pageSize);

        if (queueNames.length > 0) {
            int pageStart = (pageNum - 1) * pageSize;
            int pageEnd = pageSize * pageNum >= queueNames.length ? queueNames.length : pageSize * pageNum;
            result.addAll(Arrays.asList(queueNames).subList(pageStart, pageEnd));
        }

        return new GenericPage<>(prefixStrip(result), pageNum, pageSize, queueNames.length);
    }

    @Override
    public int getQueueTaskCount(String queueName) {
        return getQueue(ActorUtils.toPrefixed(queueName, queueNamePrefix)).size();
    }

    @Override
    public GenericPage getQueueContent(String queueName, int pageNum, int pageSize) {
        List result = new ArrayList<>();

        CachedDelayQueue queue = getQueue(ActorUtils.toPrefixed(queueName, queueNamePrefix));
        TaskQueueItem[] queueItems = queue.toArray(new TaskQueueItem[queue.size()]);

        if (queueItems.length > 0) {
            int startIndex = (pageNum - 1) * pageSize;
            int endIndex = (pageSize * pageNum >= queueItems.length) ? queueItems.length : pageSize * pageNum;
            result.addAll(Arrays.asList(queueItems).subList(startIndex, endIndex));
        }

        return new GenericPage<>(result, pageNum, pageSize, queueItems.length);
    }

    @Override
    public Map getHoveringCount(float periodSize) {
        return null;
    }

    @Override
    public GenericPage getQueuesStatsPage(int pageNum, int pageSize, String filter) {
        GenericPage result = null;
        List fullFilteredQueueNamesList = getTaskQueueNamesByPrefix(queueNamePrefix, filter, true);

        if (fullFilteredQueueNamesList != null && !fullFilteredQueueNamesList.isEmpty()) {
            int pageStart = (pageNum - 1) * pageSize;
            int pageEnd = Math.min(pageSize * pageNum, fullFilteredQueueNamesList.size());

            List queueNames = fullFilteredQueueNamesList.subList(pageStart, pageEnd);
            if (!queueNames.isEmpty()) {
                IExecutorService es = hazelcastInstance.getExecutorService(HZ_QUEUE_INFO_EXECUTOR_SERVICE);
                Map>> results = es.submitToAllMembers(new HzQueueStatTask(new ArrayList<>(queueNames), queueNamePrefix));
                List resultItems = new ArrayList<>();
                int nodes = 0;
                for (Future> nodeResultFuture : results.values()) {
                    try {
                        mergeByQueueName(resultItems, nodeResultFuture.get(5, TimeUnit.SECONDS));
                        nodes++;
                    } catch (Exception e) {
                        logger.warn("Cannot obtain QueueStatVO data from node", e);
                    }
                }

                if (!resultItems.isEmpty()) {
                    for (QueueStatVO item : resultItems) {
                        item.setNodes(nodes);
                        item.setLocal(ClusterUtils.isLocalCachedQueue(hazelcastInstance, queueNamePrefix + item.getName()));
                    }

                    result = new GenericPage<>(resultItems, pageNum, pageSize, fullFilteredQueueNamesList.size());
                }
            }
        }

        return result;
    }

    @Override
    public List getQueueNames() {
        return getTaskQueueNamesByPrefix(queueNamePrefix, null, false);
    }

    private CachedDelayQueue getQueue(String queueName) {

        CachedDelayQueue queue = queueMap.get(queueName);

        if (queue == null) {

            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                queue = queueMap.get(queueName);
                if (queue == null) {
                    queue = queueFactory.create(queueName);
                    queueMap.put(queueName, queue);
                }
            } finally {
                lock.unlock();
            }
        }

        return queue;
    }

    private List getTaskQueueNamesByPrefix(String prefix, String filter, boolean prefixStrip) {
        List result = new ArrayList<>();

        for (DistributedObject inst : hazelcastInstance.getDistributedObjects()) {
            if (inst instanceof CachedQueue) {
                String name = inst.getName();
                if (name.startsWith(prefix)) {
                    String item = prefixStrip ? name.substring(prefix.length()) : name;
                    if (StringUtils.isBlank(filter) || item.startsWith(filter)) {
                        result.add(item);
                    }
                }
            }
        }

        return result;
    }

    private List prefixStrip(List target) {
        if (queueNamePrefix == null) {
            return target;
        }

        List result = null;
        if (target != null && !target.isEmpty()) {
            result = new ArrayList<>();
            for (String item : target) {
                result.add(item.substring(queueNamePrefix.length()));
            }
        }

        return result;
    }

    private void mergeByQueueName(List mergeTo, List mergeFrom) {
        if (mergeFrom != null && !mergeFrom.isEmpty()) {
            for (QueueStatVO mergeFromItem : mergeFrom) {
                QueueStatVO mergeTarget = getItemByName(mergeTo, mergeFromItem.getName());
                if (mergeTarget != null) {
                    mergeTarget.sumValuesWith(mergeFromItem);
                } else {
                    mergeTo.add(mergeFromItem);
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy