net.sf.ehcache.distribution.MulticastRMICacheManagerPeerProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache-core Show documentation
Show all versions of ehcache-core Show documentation
Internal ehcache-core module. This artifact is not meant to be used directly
/**
* Copyright Terracotta, Inc.
*
* 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 net.sf.ehcache.distribution;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import java.io.IOException;
import java.net.InetAddress;
import java.rmi.NotBoundException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A peer provider which discovers peers using Multicast.
*
* Hosts can be in three different levels of conformance with the Multicast specification (RFC1112), according to the requirements they meet.
*
* - Level 0 is the "no support for IP Multicasting" level. Lots of hosts and routers in the Internet are in this state,
* as multicast support is not mandatory in IPv4 (it is, however, in IPv6).
* Not too much explanation is needed here: hosts in this level can neither send nor receive multicast packets.
* They must ignore the ones sent by other multicast capable hosts.
*
- Level 1 is the "support for sending but not receiving multicast IP datagrams" level.
* Thus, note that it is not necessary to join a multicast group to be able to send datagrams to it.
* Very few additions are needed in the IP module to make a "Level 0" host "Level 1-compliant".
*
- Level 2 is the "full support for IP multicasting" level.
* Level 2 hosts must be able to both send and receive multicast traffic.
* They must know the way to join and leave multicast groups and to propagate this information to multicast routers.
* Thus, they must include an Internet Group Management Protocol (IGMP) implementation in their TCP/IP stack.
*
*
* The list of CachePeers is maintained via heartbeats. rmiUrls are looked up using RMI and converted to CachePeers on
* registration. On lookup any stale references are removed.
*
* @author Greg Luck
* @version $Id: MulticastRMICacheManagerPeerProvider.java 6488 2012-10-30 17:55:04Z cdennis $
*/
public final class MulticastRMICacheManagerPeerProvider extends RMICacheManagerPeerProvider implements CacheManagerPeerProvider {
/**
* One tenth of a second, in ms
*/
protected static final int SHORT_DELAY = 100;
private static final Logger LOG = LoggerFactory.getLogger(MulticastRMICacheManagerPeerProvider.class.getName());
private final MulticastKeepaliveHeartbeatReceiver heartBeatReceiver;
private final MulticastKeepaliveHeartbeatSender heartBeatSender;
/**
* Creates and starts a multicast peer provider
*
* @param groupMulticastAddress 224.0.0.1 to 239.255.255.255 e.g. 230.0.0.1
* @param groupMulticastPort 1025 to 65536 e.g. 4446
* @param hostAddress the address of the interface to use for sending and receiving multicast. May be null.
*/
public MulticastRMICacheManagerPeerProvider(CacheManager cacheManager, InetAddress groupMulticastAddress,
Integer groupMulticastPort, Integer timeToLive, InetAddress hostAddress) {
super(cacheManager);
heartBeatReceiver = new MulticastKeepaliveHeartbeatReceiver(this, groupMulticastAddress,
groupMulticastPort, hostAddress);
heartBeatSender = new MulticastKeepaliveHeartbeatSender(cacheManager, groupMulticastAddress,
groupMulticastPort, timeToLive, hostAddress);
}
/**
* {@inheritDoc}
*/
public final void init() throws CacheException {
try {
heartBeatReceiver.init();
heartBeatSender.init();
} catch (IOException exception) {
LOG.error("Error starting heartbeat. Error was: " + exception.getMessage(), exception);
throw new CacheException(exception.getMessage());
}
}
/**
* Register a new peer, but only if the peer is new, otherwise the last seen timestamp is updated.
*
* This method is thread-safe. It relies on peerUrls being a synchronizedMap
*
* @param rmiUrl
*/
public final void registerPeer(String rmiUrl) {
try {
CachePeerEntry cachePeerEntry = (CachePeerEntry) peerUrls.get(rmiUrl);
if (cachePeerEntry == null || stale(cachePeerEntry.date)) {
//can take seconds if there is a problem
CachePeer cachePeer = lookupRemoteCachePeer(rmiUrl);
cachePeerEntry = new CachePeerEntry(cachePeer, new Date());
//synchronized due to peerUrls being a synchronizedMap
peerUrls.put(rmiUrl, cachePeerEntry);
} else {
cachePeerEntry.date = new Date();
}
} catch (IOException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Unable to lookup remote cache peer for " + rmiUrl + ". Removing from peer list. Cause was: "
+ e.getMessage());
}
unregisterPeer(rmiUrl);
} catch (NotBoundException e) {
peerUrls.remove(rmiUrl);
if (LOG.isDebugEnabled()) {
LOG.debug("Unable to lookup remote cache peer for " + rmiUrl + ". Removing from peer list. Cause was: "
+ e.getMessage());
}
} catch (Throwable t) {
LOG.error("Unable to lookup remote cache peer for " + rmiUrl
+ ". Cause was not due to an IOException or NotBoundException which will occur in normal operation:" +
" " + t.getMessage());
}
}
/**
* @return a list of {@link CachePeer} peers, excluding the local peer.
*/
public final synchronized List listRemoteCachePeers(Ehcache cache) throws CacheException {
List remoteCachePeers = new ArrayList();
List staleList = new ArrayList();
synchronized (peerUrls) {
for (Iterator iterator = peerUrls.keySet().iterator(); iterator.hasNext();) {
String rmiUrl = (String) iterator.next();
String rmiUrlCacheName = extractCacheName(rmiUrl);
try {
if (!rmiUrlCacheName.equals(cache.getName())) {
continue;
}
CachePeerEntry cachePeerEntry = (CachePeerEntry) peerUrls.get(rmiUrl);
Date date = cachePeerEntry.date;
if (!stale(date)) {
CachePeer cachePeer = cachePeerEntry.cachePeer;
remoteCachePeers.add(cachePeer);
} else {
LOG.debug("rmiUrl is stale. Either the remote peer is shutdown or the " +
"network connectivity has been interrupted. Will be removed from list of remote cache peers",
rmiUrl);
staleList.add(rmiUrl);
}
} catch (Exception exception) {
LOG.error(exception.getMessage(), exception);
throw new CacheException("Unable to list remote cache peers. Error was " + exception.getMessage());
}
}
//Must remove entries after we have finished iterating over them
for (int i = 0; i < staleList.size(); i++) {
String rmiUrl = (String) staleList.get(i);
peerUrls.remove(rmiUrl);
}
}
return remoteCachePeers;
}
/**
* Shutdown the heartbeat
*/
public final void dispose() {
heartBeatSender.dispose();
heartBeatReceiver.dispose();
}
/**
* Time for a cluster to form. This varies considerably, depending on the implementation.
*
* @return the time in ms, for a cluster to form
*/
public long getTimeForClusterToForm() {
return MulticastKeepaliveHeartbeatSender.getHeartBeatInterval() * 2 + SHORT_DELAY;
}
/**
* The time after which an unrefreshed peer provider entry is considered stale.
*/
protected long getStaleTime() {
return MulticastKeepaliveHeartbeatSender.getHeartBeatStaleTime();
}
/**
* Whether the entry should be considered stale.
* This will depend on the type of RMICacheManagerPeerProvider.
* This method should be overridden for implementations that go stale based on date
*
* @param date the date the entry was created
* @return true if stale
*/
protected final boolean stale(Date date) {
long now = System.currentTimeMillis();
return date.getTime() < (now - getStaleTime());
}
/**
* Entry containing a looked up CachePeer and date
*/
protected static final class CachePeerEntry {
private final CachePeer cachePeer;
private Date date;
/**
* Constructor
*
* @param cachePeer the cache peer part of this entry
* @param date the date part of this entry
*/
public CachePeerEntry(CachePeer cachePeer, Date date) {
this.cachePeer = cachePeer;
this.date = date;
}
/**
* @return the cache peer part of this entry
*/
public final CachePeer getCachePeer() {
return cachePeer;
}
/**
* @return the date part of this entry
*/
public final Date getDate() {
return date;
}
}
/**
* @return the MulticastKeepaliveHeartbeatReceiver
*/
public MulticastKeepaliveHeartbeatReceiver getHeartBeatReceiver() {
return heartBeatReceiver;
}
/**
* @return the MulticastKeepaliveHeartbeatSender
*/
public MulticastKeepaliveHeartbeatSender getHeartBeatSender() {
return heartBeatSender;
}
}