/* * Copyright (c) 2008-2017, 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.replicatedmap.impl; import com.hazelcast.config.Config; import com.hazelcast.config.InMemoryFormat; import com.hazelcast.config.ListenerConfig; import com.hazelcast.config.ReplicatedMapConfig; import com.hazelcast.core.DistributedObject; import com.hazelcast.core.EntryListener; import com.hazelcast.core.HazelcastInstanceAware; import com.hazelcast.core.Member; import com.hazelcast.core.MemberSelector; import com.hazelcast.internal.cluster.impl.ClusterServiceImpl; import com.hazelcast.internal.partition.InternalPartition; import com.hazelcast.internal.partition.impl.InternalPartitionServiceImpl; import com.hazelcast.internal.serialization.impl.HeapData; import com.hazelcast.monitor.LocalReplicatedMapStats; import com.hazelcast.monitor.impl.LocalReplicatedMapStatsImpl; import com.hazelcast.nio.Address; import com.hazelcast.nio.ClassLoaderUtil; import com.hazelcast.replicatedmap.ReplicatedMapCantBeCreatedOnLiteMemberException; import com.hazelcast.replicatedmap.impl.operation.CheckReplicaVersionOperation; import com.hazelcast.replicatedmap.impl.operation.ReplicationOperation; import com.hazelcast.replicatedmap.impl.record.ReplicatedRecord; import com.hazelcast.replicatedmap.impl.record.ReplicatedRecordStore; import com.hazelcast.replicatedmap.merge.MergePolicyProvider; import com.hazelcast.spi.EventPublishingService; import com.hazelcast.spi.ManagedService; import com.hazelcast.spi.MigrationAwareService; import com.hazelcast.spi.NodeEngine; import com.hazelcast.spi.Operation; import com.hazelcast.spi.OperationService; import com.hazelcast.spi.PartitionMigrationEvent; import com.hazelcast.spi.PartitionReplicationEvent; import com.hazelcast.spi.RemoteService; import com.hazelcast.spi.SplitBrainHandlerService; import com.hazelcast.spi.StatisticsAwareService; import com.hazelcast.spi.impl.eventservice.impl.TrueEventFilter; import com.hazelcast.util.ConcurrencyUtil; import com.hazelcast.util.ConstructorFunction; import com.hazelcast.util.ExceptionUtil; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import static com.hazelcast.cluster.memberselector.MemberSelectors.DATA_MEMBER_SELECTOR; /** * This is the main service implementation to handle proxy creation, event publishing, migration, anti-entropy and * manages the backing {@link PartitionContainer}s that actually hold the data */ public class ReplicatedMapService implements ManagedService, RemoteService, EventPublishingService<Object, Object>, MigrationAwareService, SplitBrainHandlerService, StatisticsAwareService { public static final String SERVICE_NAME = "hz:impl:replicatedMapService"; public static final int INVOCATION_TRY_COUNT = 3; private static final int SYNC_INTERVAL_SECONDS = 30; private final Config config; private final NodeEngine nodeEngine; private final PartitionContainer[] partitionContainers; private final InternalPartitionServiceImpl partitionService; private final ClusterServiceImpl clusterService; private final OperationService operationService; private final ReplicatedMapEventPublishingService eventPublishingService; private final MergePolicyProvider mergePolicyProvider; private final ReplicatedMapSplitBrainHandlerService replicatedMapSplitBrainHandlerService; private ConcurrentHashMap<String, LocalReplicatedMapStatsImpl> statsMap = new ConcurrentHashMap<String, LocalReplicatedMapStatsImpl>(); private ConstructorFunction<String, LocalReplicatedMapStatsImpl> constructorFunction = new ConstructorFunction<String, LocalReplicatedMapStatsImpl>() { @Override public LocalReplicatedMapStatsImpl createNew(String arg) { return new LocalReplicatedMapStatsImpl(); } }; public ReplicatedMapService(NodeEngine nodeEngine) { this.nodeEngine = nodeEngine; this.config = nodeEngine.getConfig(); this.partitionService = (InternalPartitionServiceImpl) nodeEngine.getPartitionService(); this.clusterService = (ClusterServiceImpl) nodeEngine.getClusterService(); this.operationService = nodeEngine.getOperationService(); this.partitionContainers = new PartitionContainer[nodeEngine.getPartitionService().getPartitionCount()]; this.eventPublishingService = new ReplicatedMapEventPublishingService(this); this.mergePolicyProvider = new MergePolicyProvider(nodeEngine); this.replicatedMapSplitBrainHandlerService = new ReplicatedMapSplitBrainHandlerService(this, mergePolicyProvider); } @Override public void init(final NodeEngine nodeEngine, Properties properties) { if (config.isLiteMember()) { return; } for (int i = 0; i < nodeEngine.getPartitionService().getPartitionCount(); i++) { partitionContainers[i] = new PartitionContainer(this, i); } nodeEngine.getExecutionService().getGlobalTaskScheduler().scheduleWithRepetition(new Runnable() { @Override public void run() { triggerAntiEntropy(); } }, 0, SYNC_INTERVAL_SECONDS, TimeUnit.SECONDS); } /** Send an operation to all replicas to check their replica versions for all partitions for which this node is the owner */ public void triggerAntiEntropy() { if (clusterService.getSize(DATA_MEMBER_SELECTOR) == 1) { return; } Collection<Address> addresses = new ArrayList<Address>(getMemberAddresses(DATA_MEMBER_SELECTOR)); addresses.remove(nodeEngine.getThisAddress()); for (int i = 0; i < partitionContainers.length; i++) { Address thisAddress = nodeEngine.getThisAddress(); InternalPartition partition = partitionService.getPartition(i, false); Address ownerAddress = partition.getOwnerOrNull(); if (!thisAddress.equals(ownerAddress)) { continue; } PartitionContainer partitionContainer = partitionContainers[i]; if (partitionContainer.isEmpty()) { continue; } for (Address address : addresses) { CheckReplicaVersionOperation checkReplicaVersionOperation = new CheckReplicaVersionOperation(partitionContainer); checkReplicaVersionOperation.setPartitionId(i); checkReplicaVersionOperation.setValidateTarget(false); operationService.createInvocationBuilder(SERVICE_NAME, checkReplicaVersionOperation, address) .setTryCount(INVOCATION_TRY_COUNT) .invoke(); } } } @Override public void reset() { if (config.isLiteMember()) { return; } for (int i = 0; i < nodeEngine.getPartitionService().getPartitionCount(); i++) { ConcurrentMap<String, ReplicatedRecordStore> stores = partitionContainers[i].getStores(); for (ReplicatedRecordStore store : stores.values()) { store.reset(); } } } @Override public void shutdown(boolean terminate) { if (config.isLiteMember()) { return; } for (PartitionContainer container : partitionContainers) { container.shutdown(); } } public LocalReplicatedMapStatsImpl getLocalMapStatsImpl(String name) { return ConcurrencyUtil.getOrPutIfAbsent(statsMap, name, constructorFunction); } public LocalReplicatedMapStatsImpl createReplicatedMapStats(String name) { LocalReplicatedMapStatsImpl stats = getLocalMapStatsImpl(name); long hits = 0; long count = 0; long memoryUsage = 0; boolean isBinary = (getReplicatedMapConfig(name).getInMemoryFormat() == InMemoryFormat.BINARY); for (PartitionContainer container : partitionContainers) { ReplicatedRecordStore store = container.getRecordStore(name); if (store == null) { continue; } Iterator<ReplicatedRecord> iterator = store.recordIterator(); while (iterator.hasNext()) { ReplicatedRecord record = iterator.next(); stats.setLastAccessTime(Math.max(stats.getLastAccessTime(), record.getLastAccessTime())); stats.setLastUpdateTime(Math.max(stats.getLastUpdateTime(), record.getUpdateTime())); hits += record.getHits(); if (isBinary) { memoryUsage += ((HeapData) record.getValueInternal()).getHeapCost(); } count++; } } stats.setOwnedEntryCount(count); stats.setHits(hits); stats.setOwnedEntryMemoryCost(memoryUsage); return stats; } @Override public DistributedObject createDistributedObject(String objectName) { if (config.isLiteMember()) { throw new ReplicatedMapCantBeCreatedOnLiteMemberException(nodeEngine.getThisAddress()); } for (int i = 0; i < nodeEngine.getPartitionService().getPartitionCount(); i++) { PartitionContainer partitionContainer = partitionContainers[i]; if (partitionContainer == null) { continue; } partitionContainer.getOrCreateRecordStore(objectName); } return new ReplicatedMapProxy(nodeEngine, objectName, this); } @Override public void destroyDistributedObject(String objectName) { if (config.isLiteMember()) { return; } for (int i = 0; i < nodeEngine.getPartitionService().getPartitionCount(); i++) { partitionContainers[i].destroy(objectName); } } @Override public void dispatchEvent(Object event, Object listener) { eventPublishingService.dispatchEvent(event, listener); } public ReplicatedMapConfig getReplicatedMapConfig(String name) { return config.getReplicatedMapConfig(name).getAsReadOnly(); } public ReplicatedRecordStore getReplicatedRecordStore(String name, boolean create, Object key) { return getReplicatedRecordStore(name, create, partitionService.getPartitionId(key)); } public ReplicatedRecordStore getReplicatedRecordStore(String name, boolean create, int partitionId) { if (config.isLiteMember()) { throw new ReplicatedMapCantBeCreatedOnLiteMemberException(nodeEngine.getThisAddress()); } PartitionContainer partitionContainer = partitionContainers[partitionId]; if (create) { return partitionContainer.getOrCreateRecordStore(name); } return partitionContainer.getRecordStore(name); } public Collection<ReplicatedRecordStore> getAllReplicatedRecordStores(String name) { int partitionCount = nodeEngine.getPartitionService().getPartitionCount(); ArrayList<ReplicatedRecordStore> stores = new ArrayList<ReplicatedRecordStore>(partitionCount); for (int i = 0; i < partitionCount; i++) { PartitionContainer partitionContainer = partitionContainers[i]; if (partitionContainer == null) { continue; } ReplicatedRecordStore recordStore = partitionContainer.getRecordStore(name); if (recordStore == null) { continue; } stores.add(recordStore); } return stores; } private Collection<Address> getMemberAddresses(MemberSelector memberSelector) { Collection<Member> members = clusterService.getMembers(memberSelector); Collection<Address> addresses = new ArrayList<Address>(members.size()); for (Member member : members) { addresses.add(member.getAddress()); } return addresses; } public void initializeListeners(String name) { List<ListenerConfig> listenerConfigs = config.getReplicatedMapConfig(name).getListenerConfigs(); for (ListenerConfig listenerConfig : listenerConfigs) { EntryListener listener = null; if (listenerConfig.getImplementation() != null) { listener = (EntryListener) listenerConfig.getImplementation(); } else if (listenerConfig.getClassName() != null) { try { listener = ClassLoaderUtil.newInstance(nodeEngine.getConfigClassLoader(), listenerConfig.getClassName()); } catch (Exception e) { throw ExceptionUtil.rethrow(e); } } if (listener != null) { if (listener instanceof HazelcastInstanceAware) { ((HazelcastInstanceAware) listener).setHazelcastInstance(nodeEngine.getHazelcastInstance()); } eventPublishingService.addEventListener(listener, TrueEventFilter.INSTANCE, name); } } } public PartitionContainer getPartitionContainer(int partitionId) { return partitionContainers[partitionId]; } public NodeEngine getNodeEngine() { return nodeEngine; } public ReplicatedMapEventPublishingService getEventPublishingService() { return eventPublishingService; } @Override public Operation prepareReplicationOperation(PartitionReplicationEvent event) { if (config.isLiteMember()) { return null; } if (event.getReplicaIndex() > 0) { return null; } final PartitionContainer container = partitionContainers[event.getPartitionId()]; final ReplicationOperation operation = new ReplicationOperation(nodeEngine.getSerializationService(), container, event.getPartitionId()); operation.setService(this); return operation.isEmpty() ? null : operation; } @Override public void beforeMigration(PartitionMigrationEvent event) { // no-op } @Override public void commitMigration(PartitionMigrationEvent event) { // no-op } @Override public void rollbackMigration(PartitionMigrationEvent event) { // no-op } @Override public Runnable prepareMergeRunnable() { return replicatedMapSplitBrainHandlerService.prepareMergeRunnable(); } @Override public Map<String, LocalReplicatedMapStats> getStats() { Collection<String> maps = getNodeEngine().getProxyService().getDistributedObjectNames(SERVICE_NAME); Map<String, LocalReplicatedMapStats> mapStats = new HashMap<String, LocalReplicatedMapStats>(maps.size()); for (String map : maps) { mapStats.put(map, createReplicatedMapStats(map)); } return mapStats; } }