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

com.hazelcast.map.impl.nearcache.BatchInvalidator Maven / Gradle / Ivy

/*
 * Copyright (c) 2008-2016, Hazelcast, Inc. All Rights Reserved.
 *
 * 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.hazelcast.map.impl.nearcache;

import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.LifecycleEvent;
import com.hazelcast.core.LifecycleListener;
import com.hazelcast.core.LifecycleService;
import com.hazelcast.core.Member;
import com.hazelcast.instance.GroupProperties;
import com.hazelcast.map.impl.EventListenerFilter;
import com.hazelcast.map.impl.MapServiceContext;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.spi.EventFilter;
import com.hazelcast.spi.EventRegistration;
import com.hazelcast.spi.ExecutionService;
import com.hazelcast.spi.Operation;
import com.hazelcast.util.ConstructorFunction;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import static com.hazelcast.core.EntryEventType.INVALIDATION;
import static com.hazelcast.instance.GroupProperty.MAP_INVALIDATION_MESSAGE_BATCH_FREQUENCY_SECONDS;
import static com.hazelcast.instance.GroupProperty.MAP_INVALIDATION_MESSAGE_BATCH_SIZE;
import static com.hazelcast.map.impl.MapService.SERVICE_NAME;
import static com.hazelcast.util.ConcurrencyUtil.getOrPutIfAbsent;
import static java.util.Collections.EMPTY_LIST;

/**
 * Sends invalidations to near-caches in batches.
 */
public class BatchInvalidator extends AbstractNearCacheInvalidator {

    private static final String INVALIDATION_EXECUTOR_NAME = BatchInvalidator.class.getName();

    /**
     * Creates an invalidation-queue for a map.
     */
    private final ConstructorFunction invalidationQueueConstructor
            = new ConstructorFunction() {
        @Override
        public InvalidationQueue createNew(String mapName) {
            return new InvalidationQueue();
        }
    };

    /**
     * map-name to invalidation-queue mappings.
     */
    private final ConcurrentMap invalidationQueues
            = new ConcurrentHashMap();

    private String listenerRegistrationId;
    private final int batchSize;

    BatchInvalidator(MapServiceContext mapServiceContext, NearCacheProvider nearCacheProvider) {
        super(mapServiceContext, nearCacheProvider);
        this.batchSize = getBatchSize();
        startBackgroundBatchProcessor();
        handleBatchesOnNodeShutdown();
    }

    @Override
    public void invalidate(String mapName, Data key, String sourceUuid) {
        invalidateInternal(mapName, key, null, sourceUuid);
    }

    @Override
    public void invalidate(String mapName, List keys, String sourceUuid) {
        invalidateInternal(mapName, null, keys, sourceUuid);
    }

    @Override
    public void clear(String mapName, boolean owner, String sourceUuid) {
        if (owner) {
            // only send invalidation event to clients, server near-caches are cleared by ClearOperation.
            invalidateClient(new CleaningNearCacheInvalidation(mapName, sourceUuid));
        }

        clearLocal(mapName);
    }

    private void invalidateInternal(String mapName, Data key, List keys, String sourceUuid) {
        accumulateOrInvalidate(mapName, key, keys, sourceUuid);
        invalidateLocal(mapName, key, keys);
    }

    @Override
    public void destroy(String mapName) {
        InvalidationQueue invalidationQueue = invalidationQueues.remove(mapName);
        if (invalidationQueue != null) {
            invalidateClient(new CleaningNearCacheInvalidation(mapName, null));
        }
    }

    @Override
    public void shutdown() {
        ExecutionService executionService = nodeEngine.getExecutionService();
        executionService.shutdownExecutor(INVALIDATION_EXECUTOR_NAME);

        HazelcastInstance node = nodeEngine.getHazelcastInstance();
        LifecycleService lifecycleService = node.getLifecycleService();
        lifecycleService.removeLifecycleListener(listenerRegistrationId);

        invalidationQueues.clear();
    }

    @Override
    public void reset() {
        invalidationQueues.clear();
    }

    public void accumulateOrInvalidate(String mapName, Data key, List keys, String sourceUuid) {
        if (!mapServiceContext.getMapContainer(mapName).isInvalidationEnabled()) {
            return;
        }

        InvalidationQueue invalidationQueue = getOrPutIfAbsent(invalidationQueues, mapName, invalidationQueueConstructor);

        if (key != null) {
            invalidationQueue.offer(new SingleNearCacheInvalidation(mapName, toHeapData(key), sourceUuid));
        }

        if (keys != null) {
            for (Data data : keys) {
                invalidationQueue.offer(new SingleNearCacheInvalidation(mapName, toHeapData(data), sourceUuid));
            }
        }

        if (invalidationQueue.size() >= batchSize) {
            sendBatch(mapName, invalidationQueue);
        }
    }

    private void sendBatch(String mapName, InvalidationQueue invalidationQueue) {
        if (invalidationQueue == null) {
            return;
        }
        // If still in progress, no need to another attempt. So just return.
        if (!invalidationQueue.tryAcquire()) {
            return;
        }

        int size = Math.min(batchSize, invalidationQueue.size());
        BatchNearCacheInvalidation batch = new BatchNearCacheInvalidation(mapName, size);
        for (int i = 0; i < size; i++) {
            SingleNearCacheInvalidation invalidation = invalidationQueue.poll();
            if (invalidation == null) {
                break;
            }
            batch.add(invalidation);
        }

        try {
            invalidateMember(batch);
            invalidateClient(batch);
        } finally {
            invalidationQueue.release();
        }
    }

    private void invalidateClient(Invalidation invalidation) {
        String mapName = invalidation.getName();
        if (!hasInvalidationListener(mapName)) {
            return;
        }

        Collection registrations = eventService.getRegistrations(SERVICE_NAME, mapName);
        for (EventRegistration registration : registrations) {
            EventFilter filter = registration.getFilter();
            if (filter instanceof EventListenerFilter && filter.eval(INVALIDATION.getType())) {
                Object orderKey = getOrderKey(mapName, invalidation);
                eventService.publishEvent(SERVICE_NAME, registration, invalidation, orderKey.hashCode());
            }
        }
    }

    protected void invalidateMember(BatchNearCacheInvalidation batch) {
        String mapName = batch.getName();
        if (!isMemberNearCacheInvalidationEnabled(mapName)) {
            return;
        }

        Operation operation = null;
        Collection members = clusterService.getMembers();
        for (Member member : members) {
            if (member.localMember()) {
                continue;
            }

            if (operation == null) {
                operation = createSingleOrBatchInvalidationOperation(mapName, null, getKeys(batch));
            }

            operationService.send(operation, member.getAddress());
        }
    }

    public static List getKeys(BatchNearCacheInvalidation batch) {
        return getKeysExcludingSource(batch, null);
    }

    public static List getKeysExcludingSource(BatchNearCacheInvalidation batch, String excludedSourceUuid) {
        List invalidations = batch.getInvalidations();

        List keyList = null;
        for (SingleNearCacheInvalidation invalidation : invalidations) {
            if (excludedSourceUuid == null || !invalidation.getSourceUuid().equals(excludedSourceUuid)) {
                if (keyList == null) {
                    keyList = new ArrayList(invalidations.size());
                }
                keyList.add(invalidation.getKey());
            }
        }

        return keyList == null ? EMPTY_LIST : keyList;
    }


    private void handleBatchesOnNodeShutdown() {
        HazelcastInstance node = nodeEngine.getHazelcastInstance();
        LifecycleService lifecycleService = node.getLifecycleService();
        listenerRegistrationId = lifecycleService.addLifecycleListener(new LifecycleListener() {
            @Override
            public void stateChanged(LifecycleEvent event) {
                if (event.getState() == LifecycleEvent.LifecycleState.SHUTTING_DOWN) {
                    Set> entries = invalidationQueues.entrySet();
                    for (Map.Entry entry : entries) {
                        sendBatch(entry.getKey(), entry.getValue());
                    }
                }
            }
        });
    }

    private void startBackgroundBatchProcessor() {
        int periodSeconds = getBackgroundProcessorRunPeriodSeconds();
        ExecutionService executionService = nodeEngine.getExecutionService();
        executionService.scheduleAtFixedRate(INVALIDATION_EXECUTOR_NAME,
                new MapBatchInvalidationEventSender(), periodSeconds, periodSeconds, TimeUnit.SECONDS);

    }

    private int getBatchSize() {
        GroupProperties groupProperties = nodeEngine.getGroupProperties();
        return groupProperties.getInteger(MAP_INVALIDATION_MESSAGE_BATCH_SIZE);
    }

    private int getBackgroundProcessorRunPeriodSeconds() {
        GroupProperties groupProperties = nodeEngine.getGroupProperties();
        return groupProperties.getInteger(MAP_INVALIDATION_MESSAGE_BATCH_FREQUENCY_SECONDS);
    }

    /**
     * A background runner which runs periodically and consumes invalidation queues.
     */
    private class MapBatchInvalidationEventSender implements Runnable {

        @Override
        public void run() {
            for (Map.Entry entry : invalidationQueues.entrySet()) {
                if (Thread.currentThread().isInterrupted()) {
                    break;
                }
                String mapName = entry.getKey();
                InvalidationQueue invalidationQueue = entry.getValue();
                if (invalidationQueue.size() > 0) {
                    sendBatch(mapName, invalidationQueue);
                }
            }
        }
    }

    private static class InvalidationQueue extends ConcurrentLinkedQueue {

        private final AtomicInteger elementCount = new AtomicInteger(0);
        private final AtomicBoolean flushingInProgress = new AtomicBoolean(false);

        @Override
        public int size() {
            return elementCount.get();
        }

        @Override
        public boolean offer(SingleNearCacheInvalidation invalidation) {
            boolean offered = super.offer(invalidation);
            if (offered) {
                elementCount.incrementAndGet();
            }
            return offered;
        }

        @Override
        public SingleNearCacheInvalidation poll() {
            SingleNearCacheInvalidation invalidation = super.poll();
            if (invalidation != null) {
                elementCount.decrementAndGet();
            }
            return invalidation;
        }

        public boolean tryAcquire() {
            return flushingInProgress.compareAndSet(false, true);
        }

        public void release() {
            flushingInProgress.set(false);
        }

        @Override
        public boolean add(SingleNearCacheInvalidation invalidation) {
            throw new UnsupportedOperationException();
        }

        @Override
        public SingleNearCacheInvalidation remove() {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean remove(Object o) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean addAll(Collection c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean removeAll(Collection c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean retainAll(Collection c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void clear() {
            throw new UnsupportedOperationException();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy