Commit bfdbe692 authored by Tom Evans's avatar Tom Evans Committed by tevans

OF-602: Hazelcast clustering plugin improvements (install/upgrade/docs)

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@13396 b35dd754-fafc-0310-a699-88a17e54d16e
parent ea22a488
...@@ -34,8 +34,6 @@ import org.jivesoftware.openfire.PacketRouter; ...@@ -34,8 +34,6 @@ import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.RoutableChannelHandler; import org.jivesoftware.openfire.RoutableChannelHandler;
import org.jivesoftware.openfire.RoutingTable; import org.jivesoftware.openfire.RoutingTable;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.cluster.ClusterEventListener;
import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.commands.AdHocCommandManager; import org.jivesoftware.openfire.commands.AdHocCommandManager;
import org.jivesoftware.openfire.component.InternalComponentManager; import org.jivesoftware.openfire.component.InternalComponentManager;
import org.jivesoftware.openfire.container.BasicModule; import org.jivesoftware.openfire.container.BasicModule;
...@@ -68,7 +66,7 @@ import org.xmpp.packet.Presence; ...@@ -68,7 +66,7 @@ import org.xmpp.packet.Presence;
* @author Matt Tucker * @author Matt Tucker
*/ */
public class PubSubModule extends BasicModule implements ServerItemsProvider, DiscoInfoProvider, public class PubSubModule extends BasicModule implements ServerItemsProvider, DiscoInfoProvider,
DiscoItemsProvider, RoutableChannelHandler, PubSubService, ClusterEventListener, PropertyEventListener { DiscoItemsProvider, RoutableChannelHandler, PubSubService, PropertyEventListener {
private static final Logger Log = LoggerFactory.getLogger(PubSubModule.class); private static final Logger Log = LoggerFactory.getLogger(PubSubModule.class);
...@@ -434,8 +432,6 @@ public class PubSubModule extends BasicModule implements ServerItemsProvider, Di ...@@ -434,8 +432,6 @@ public class PubSubModule extends BasicModule implements ServerItemsProvider, Di
else { else {
rootCollectionNode = (CollectionNode) getNode(rootNodeID); rootCollectionNode = (CollectionNode) getNode(rootNodeID);
} }
// Listen to cluster events
ClusterManager.addListener(this);
} }
@Override @Override
...@@ -503,24 +499,6 @@ public class PubSubModule extends BasicModule implements ServerItemsProvider, Di ...@@ -503,24 +499,6 @@ public class PubSubModule extends BasicModule implements ServerItemsProvider, Di
return serviceEnabled; return serviceEnabled;
} }
public void joinedCluster() {
// Disable the service until we know that we are the senior cluster member
// enableService(false);
}
public void joinedCluster(byte[] nodeID) {
// Do nothing
}
public void leftCluster() {
// Offer the service when not running in a cluster
// enableService(true);
}
public void leftCluster(byte[] nodeID) {
// Do nothing
}
public void markedAsSeniorClusterMember() { public void markedAsSeniorClusterMember() {
// Offer the service since we are the senior cluster member // Offer the service since we are the senior cluster member
// enableService(true); // enableService(true);
......
...@@ -435,7 +435,16 @@ public class CacheFactory { ...@@ -435,7 +435,16 @@ public class CacheFactory {
* this JVM to join a cluster. * this JVM to join a cluster.
*/ */
public static boolean isClusteringAvailable() { public static boolean isClusteringAvailable() {
return getMaxClusterNodes() > 1; if (clusteredCacheFactoryStrategy == null) {
try {
clusteredCacheFactoryStrategy = (CacheFactoryStrategy) Class.forName(
clusteredCacheFactoryClass, true,
getClusteredCacheStrategyClassLoader()).newInstance();
} catch (Exception e) {
log.warn("Clustered cache factory strategy " + clusteredCacheFactoryClass + " not found");
}
}
return (clusteredCacheFactoryStrategy != null);
} }
/** /**
...@@ -609,13 +618,9 @@ public class CacheFactory { ...@@ -609,13 +618,9 @@ public class CacheFactory {
} }
public static void startClustering() { public static void startClustering() {
try { if (isClusteringAvailable()) {
clusteredCacheFactoryStrategy = (CacheFactoryStrategy) Class.forName(clusteredCacheFactoryClass, true, clusteringStarting = clusteredCacheFactoryStrategy.startCluster();
getClusteredCacheStrategyClassLoader()).newInstance(); }
clusteringStarting = clusteredCacheFactoryStrategy.startCluster();
} catch (Exception e) {
log.error("Clustered cache factory strategy " + clusteredCacheFactoryClass + " not found", e);
}
if (clusteringStarting) { if (clusteringStarting) {
if (statsThread == null) { if (statsThread == null) {
// Start a timing thread with 1 second of accuracy. // Start a timing thread with 1 second of accuracy.
...@@ -677,6 +682,7 @@ public class CacheFactory { ...@@ -677,6 +682,7 @@ public class CacheFactory {
public static void stopClustering() { public static void stopClustering() {
// Stop the cluster // Stop the cluster
clusteredCacheFactoryStrategy.stopCluster(); clusteredCacheFactoryStrategy.stopCluster();
clusteredCacheFactoryStrategy = null;
// Set the strategy to local // Set the strategy to local
cacheFactoryStrategy = localCacheFactoryStrategy; cacheFactoryStrategy = localCacheFactoryStrategy;
} }
...@@ -691,7 +697,6 @@ public class CacheFactory { ...@@ -691,7 +697,6 @@ public class CacheFactory {
for (Cache cache : getAllCaches()) { for (Cache cache : getAllCaches()) {
// skip local-only caches // skip local-only caches
if (localOnly.contains(cache.getName())) continue; if (localOnly.contains(cache.getName())) continue;
cache.clear();
CacheWrapper cacheWrapper = ((CacheWrapper) cache); CacheWrapper cacheWrapper = ((CacheWrapper) cache);
Cache clusteredCache = cacheFactoryStrategy.createCache(cacheWrapper.getName()); Cache clusteredCache = cacheFactoryStrategy.createCache(cacheWrapper.getName());
cacheWrapper.setWrappedCache(clusteredCache); cacheWrapper.setWrappedCache(clusteredCache);
...@@ -713,7 +718,6 @@ public class CacheFactory { ...@@ -713,7 +718,6 @@ public class CacheFactory {
for (Cache cache : getAllCaches()) { for (Cache cache : getAllCaches()) {
// skip local-only caches // skip local-only caches
if (localOnly.contains(cache.getName())) continue; if (localOnly.contains(cache.getName())) continue;
cache.clear();
CacheWrapper cacheWrapper = ((CacheWrapper) cache); CacheWrapper cacheWrapper = ((CacheWrapper) cache);
Cache standaloneCache = cacheFactoryStrategy.createCache(cacheWrapper.getName()); Cache standaloneCache = cacheFactoryStrategy.createCache(cacheWrapper.getName());
cacheWrapper.setWrappedCache(standaloneCache); cacheWrapper.setWrappedCache(standaloneCache);
......
...@@ -20,10 +20,10 @@ ...@@ -20,10 +20,10 @@
package com.jivesoftware.openfire; package com.jivesoftware.openfire;
import com.jivesoftware.openfire.session.RemoteSessionLocator; import java.io.File;
import com.jivesoftware.util.cache.CoherenceExternalizableUtil; import java.io.FileFilter;
import com.jivesoftware.util.cluster.CoherencePacketRouter; import java.net.MalformedURLException;
import com.tangosol.net.CacheFactory; import java.net.URL;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.cluster.ClusterManager; import org.jivesoftware.openfire.cluster.ClusterManager;
...@@ -31,36 +31,18 @@ import org.jivesoftware.openfire.container.Plugin; ...@@ -31,36 +31,18 @@ import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager; import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.jivesoftware.util.cache.ExternalizableUtilStrategy;
import java.io.File; import com.tangosol.net.CacheFactory;
import java.io.FileFilter;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
/** /**
* Clustering Enterprise plugin. * Clustering Enterprise plugin.
* *
* @author Matt Tucker * @author Matt Tucker
*/ */
public class ClusteringPlugin implements Plugin, PropertyEventListener { public class ClusteringPlugin implements Plugin {
private static final String COHERENCE_CONFIG = "tangosol-coherence-override"; private static final String COHERENCE_CONFIG = "tangosol-coherence-override";
private static final String COHERENCE_CACHE_CONFIG = "coherence-cache-config"; private static final String COHERENCE_CACHE_CONFIG = "coherence-cache-config";
/**
* Keep serialization strategy the server was using before we set our strategy. We will
* restore old strategy when plugin is unloaded.
*/
private ExternalizableUtilStrategy serializationStrategy;
public void initializePlugin(PluginManager manager, File pluginDirectory) { public void initializePlugin(PluginManager manager, File pluginDirectory) {
System.out.println("Starting Clustering Plugin"); System.out.println("Starting Clustering Plugin");
...@@ -101,9 +83,6 @@ public class ClusteringPlugin implements Plugin, PropertyEventListener { ...@@ -101,9 +83,6 @@ public class ClusteringPlugin implements Plugin, PropertyEventListener {
// "tangosol.jar, coherence.jar and coherence-work.jar files to [OPENFIRE_HOME]/lib and restart the server."); // "tangosol.jar, coherence.jar and coherence-work.jar files to [OPENFIRE_HOME]/lib and restart the server.");
// } // }
// List for clustering setting events (e.g. enabled/disabled)
PropertyEventDispatcher.addListener(this);
// Delete no longer used COHERENCE_CONFIG file. Java system properties should be used // Delete no longer used COHERENCE_CONFIG file. Java system properties should be used
// to customize coherence // to customize coherence
File configFile = new File(enterpriseDir, COHERENCE_CONFIG + ".xml"); File configFile = new File(enterpriseDir, COHERENCE_CONFIG + ".xml");
...@@ -127,76 +106,14 @@ public class ClusteringPlugin implements Plugin, PropertyEventListener { ...@@ -127,76 +106,14 @@ public class ClusteringPlugin implements Plugin, PropertyEventListener {
catch (MalformedURLException e) { catch (MalformedURLException e) {
Log.error("Error adding openfireHome/enterprise to the classpath of the enterprise plugin", e); Log.error("Error adding openfireHome/enterprise to the classpath of the enterprise plugin", e);
} }
if (ClusterManager.isClusteringEnabled()) {
initForClustering();
// Start up or join the cluster and initialize caches
ClusterManager.startup();
}
}
private void initForClustering() {
// Set the serialization strategy to use for transmitting objects between node clusters
serializationStrategy = ExternalizableUtil.getInstance().getStrategy();
ExternalizableUtil.getInstance().setStrategy(new CoherenceExternalizableUtil());
// Set session locator to use when in a cluster
XMPPServer.getInstance().setRemoteSessionLocator(new RemoteSessionLocator());
// Set packet router to use to deliver packets to remote cluster nodes
XMPPServer.getInstance().getRoutingTable().setRemotePacketRouter(new CoherencePacketRouter());
// Initialize the Coherence cluster configuration
CacheFactory.getClusterConfig(); CacheFactory.getClusterConfig();
} ClusterManager.startup();
/**
* Returns the date when this release of Openfire Enterprise was released.
*
* @return the date when this release of Openfire Enterprise was released.
*/
public static Date getReleaseDate() {
try {
// @DATE@ should be replaced with a date with the following format: Jan 31, 2007
// Automaticly set by ANT build tasks
return DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.US).parse("@DATE@");
}
catch (ParseException e) {
Log.error("Error parsing date", e);
return null;
}
} }
public void destroyPlugin() { public void destroyPlugin() {
ClusterManager.shutdown(); // Shutdown is initiated by XMPPServer before unloading plugins
if (!XMPPServer.getInstance().isShuttingDown()) {
// Set the old serialization strategy was using before enterprise was loaded ClusterManager.shutdown();
ExternalizableUtil.getInstance().setStrategy(serializationStrategy); }
// Stop listing for clustering setting events (e.g. enabled/disabled)
PropertyEventDispatcher.removeListener(this);
}
public void propertySet(String property, Map<String, Object> params) {
// Ignore
}
public void propertyDeleted(String property, Map<String, Object> params) {
// Ignore
}
public void xmlPropertySet(String property, Map<String, Object> params) {
if (ClusterManager.CLUSTER_PROPERTY_NAME.equals(property)) {
if (Boolean.parseBoolean((String) params.get("value"))) {
// Clustering was enabled
initForClustering();
}
else {
// Clustering was disabled
}
}
}
public void xmlPropertyDeleted(String property, Map<String, Object> params) {
// Do nothing
} }
} }
...@@ -252,8 +252,6 @@ public class ClusterListener implements MemberListener { ...@@ -252,8 +252,6 @@ public class ClusterListener implements MemberListener {
// Clean up all traces. This will set all remote sessions as unavailable // Clean up all traces. This will set all remote sessions as unavailable
List<NodeID> nodeIDs = new ArrayList<NodeID>(nodeSessions.keySet()); List<NodeID> nodeIDs = new ArrayList<NodeID>(nodeSessions.keySet());
// Revert cluster caches to local caches
CacheFactory.leftCluster();
// Trigger event. Wait until the listeners have processed the event. Caches will be populated // Trigger event. Wait until the listeners have processed the event. Caches will be populated
// again with local content. // again with local content.
ClusterManager.fireLeftCluster(); ClusterManager.fireLeftCluster();
......
...@@ -19,8 +19,19 @@ ...@@ -19,8 +19,19 @@
package com.jivesoftware.util.cache; package com.jivesoftware.util.cache;
import com.jivesoftware.util.cluster.CoherenceClusterNodeInfo; import java.util.ArrayList;
import com.tangosol.net.*; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.cluster.ClusterManager; import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.cluster.ClusterNodeInfo; import org.jivesoftware.openfire.cluster.ClusterNodeInfo;
...@@ -30,11 +41,17 @@ import org.jivesoftware.util.cache.Cache; ...@@ -30,11 +41,17 @@ import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactoryStrategy; import org.jivesoftware.util.cache.CacheFactoryStrategy;
import org.jivesoftware.util.cache.CacheWrapper; import org.jivesoftware.util.cache.CacheWrapper;
import org.jivesoftware.util.cache.ClusterTask; import org.jivesoftware.util.cache.ClusterTask;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.jivesoftware.util.cache.ExternalizableUtilStrategy;
import java.util.*; import com.jivesoftware.openfire.session.RemoteSessionLocator;
import java.util.concurrent.TimeUnit; import com.jivesoftware.util.cluster.CoherenceClusterNodeInfo;
import java.util.concurrent.locks.Condition; import com.jivesoftware.util.cluster.CoherencePacketRouter;
import java.util.concurrent.locks.Lock; import com.tangosol.net.AbstractInvocable;
import com.tangosol.net.Cluster;
import com.tangosol.net.Invocable;
import com.tangosol.net.InvocationService;
import com.tangosol.net.Member;
/** /**
* CacheFactory implementation to use when using Coherence in cluster mode. * CacheFactory implementation to use when using Coherence in cluster mode.
...@@ -43,6 +60,12 @@ import java.util.concurrent.locks.Lock; ...@@ -43,6 +60,12 @@ import java.util.concurrent.locks.Lock;
*/ */
public class ClusteredCacheFactory implements CacheFactoryStrategy { public class ClusteredCacheFactory implements CacheFactoryStrategy {
/**
* Keep serialization strategy the server was using before we set our strategy. We will
* restore old strategy when plugin is unloaded.
*/
private ExternalizableUtilStrategy serializationStrategy;
/** /**
* Storage for cache statistics * Storage for cache statistics
*/ */
...@@ -69,6 +92,14 @@ public class ClusteredCacheFactory implements CacheFactoryStrategy { ...@@ -69,6 +92,14 @@ public class ClusteredCacheFactory implements CacheFactoryStrategy {
ClassLoader oldLoader = null; ClassLoader oldLoader = null;
// Set that we are starting up the cluster service // Set that we are starting up the cluster service
state = State.starting; state = State.starting;
// Set the serialization strategy to use for transmitting objects between node clusters
serializationStrategy = ExternalizableUtil.getInstance().getStrategy();
ExternalizableUtil.getInstance().setStrategy(new CoherenceExternalizableUtil());
// Set session locator to use when in a cluster
XMPPServer.getInstance().setRemoteSessionLocator(new RemoteSessionLocator());
// Set packet router to use to deliver packets to remote cluster nodes
XMPPServer.getInstance().getRoutingTable().setRemotePacketRouter(new CoherencePacketRouter());
// Initialize the Coherence cluster configuration
try { try {
// Store previous class loader (in case we change it) // Store previous class loader (in case we change it)
oldLoader = Thread.currentThread().getContextClassLoader(); oldLoader = Thread.currentThread().getContextClassLoader();
...@@ -158,6 +189,13 @@ public class ClusteredCacheFactory implements CacheFactoryStrategy { ...@@ -158,6 +189,13 @@ public class ClusteredCacheFactory implements CacheFactoryStrategy {
} }
// Reset the node ID // Reset the node ID
XMPPServer.getInstance().setNodeID(null); XMPPServer.getInstance().setNodeID(null);
// Reset packet router to use to deliver packets to remote cluster nodes
XMPPServer.getInstance().getRoutingTable().setRemotePacketRouter(null);
// Reset the session locator to use
XMPPServer.getInstance().setRemoteSessionLocator(null);
// Set the old serialization strategy was using before clustering was loaded
ExternalizableUtil.getInstance().setStrategy(serializationStrategy);
} }
public Cache createCache(String name) { public Cache createCache(String name) {
......
...@@ -44,7 +44,16 @@ ...@@ -44,7 +44,16 @@
Hazelcast Clustering Plugin Changelog Hazelcast Clustering Plugin Changelog
</h1> </h1>
<p><b>1.0.2</b> -- January 8, 2013</p> <p><b>1.0.3</b> -- January 15, 2013</p>
<p>Minor improvements for plugin installation and upgrade:</p>
<ul>
<li>Modify timing for cluster startup/shutdown operations to improve plugin installation and upgrade.</li>
<li>Defer initialization of cluster event dispatcher thread until cluster is actually enabled.</li>
<li>Improved <code>readme.html</code> to describe upgrade procedures, DNS round-robin configuration, etc.</li>
<li>Upgraded Hazelcast to version 2.5.</li>
</ul>
<p><b>1.0.2</b> -- January 9, 2013</p>
<p>This release addresses a number of issues and other feedback received via the <p>This release addresses a number of issues and other feedback received via the
<a href="http://community.igniterealtime.org/message/224947#224947">Hazelcast announcement</a> <a href="http://community.igniterealtime.org/message/224947#224947">Hazelcast announcement</a>
posted in the Openfire community forum:</p> posted in the Openfire community forum:</p>
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<name>${plugin.name}</name> <name>${plugin.name}</name>
<description>${plugin.description}</description> <description>${plugin.description}</description>
<author>Tom Evans</author> <author>Tom Evans</author>
<version>1.0.2</version> <version>1.0.3</version>
<date>01/09/2013</date> <date>01/15/2013</date>
<minServerVersion>3.7.2</minServerVersion> <minServerVersion>3.7.2</minServerVersion>
</plugin> </plugin>
...@@ -12,9 +12,13 @@ ...@@ -12,9 +12,13 @@
font-size : 0.8em; font-size : 0.8em;
} }
H2 { H2 {
font-size : 10pt; font-size : 11pt;
font-weight : bold; font-weight : bold;
} }
H3 {
font-size : 10pt;
font-style : italic;
}
A:hover { A:hover {
text-decoration : none; text-decoration : none;
} }
...@@ -53,7 +57,7 @@ ...@@ -53,7 +57,7 @@
<h2>Overview</h2> <h2>Overview</h2>
<p> <p>
The Hazelcast plugin adds support for running multiple redundant Openfire The Hazelcast plugin adds support for running multiple redundant Openfire
servers together in a cluster. By running Openfire in a cluster, you can servers together in a cluster. By running Openfire as a cluster, you can
distribute the connection load among several servers, while also providing distribute the connection load among several servers, while also providing
failover in the event that one of your servers fails. This plugin is a failover in the event that one of your servers fails. This plugin is a
drop-in replacement for the original Openfire clustering plugin, using the drop-in replacement for the original Openfire clustering plugin, using the
...@@ -61,32 +65,98 @@ open source <a href="http://www.hazelcast.com">Hazelcast</a> data distribution ...@@ -61,32 +65,98 @@ open source <a href="http://www.hazelcast.com">Hazelcast</a> data distribution
framework in lieu of an expensive proprietary third-party product. framework in lieu of an expensive proprietary third-party product.
</p> </p>
<p> <p>
The current Hazelcast release is version 2.4.1. The current Hazelcast release is version 2.5.
</p> </p>
<h2>Installation</h2> <h2>Installation</h2>
<p> <p>
To install Hazelcast, simply drop the hazelcast.jar into $OPENFIRE_HOME/plugins along To create an Openfire cluster, you should have at least two Openfire servers,
with any other plugins you may have installed. Note that Hazelcast and the original and each server must have the Hazelcast plugin installed. To install Hazelcast,
Openfire clustering plugin (clustering.jar) are mutually exclusive. You will need to simply drop the hazelcast.jar into $OPENFIRE_HOME/plugins along with any other
remove the clustering plugin before installing Hazelcast into your Openfire instance. plugins you may have installed. You may also use the Plugins page from the
admin console to install the plugin. Note that all servers in a given cluster
must be configured to share a single external database (not the Embedded DB).
</p> </p>
<p> <p>
To create an Openfire cluster, you will need at least two separate Openfire servers, By default during the Openfire startup/initialization process, the servers
and each server must have the Hazelcast plugin installed. By default, the servers
will discover each other by exchanging UDP (multicast) packets via a configurable will discover each other by exchanging UDP (multicast) packets via a configurable
IP address and port, but other initialization options are available if your network IP address and port. However, be advised that many other initialization options
does not support multicast communication (see "Configuration" below). are available and may be used if your network does not support multicast
communication (see <a href="#config">Configuration</a> below).
</p>
<p>After the Hazelcast plugin has been deployed to each of the servers, use the
radio button controls located on the Clustering page in the admin console to
activate/enable the cluster. You only need to enable clustering once; the change
will be propagated to the other servers automatically. After refreshing the
Clustering page you will be able to see all the servers that have successfully
joined the cluster.
</p>
<p>
Note that Hazelcast and the earlier clustering plugins (clustering.jar and enterprise.jar)
are mutually exclusive. You will need to remove any existing older clustering plugin(s)
before installing Hazelcast into your Openfire server(s).
</p> </p>
<p> <p>
In addition, you will need some form of load balancer to distribute the connection With your cluster up and running, you will now want some form of load balancer to
load among the members of your Openfire cluster. There are several commercial and distribute the connection load among the members of your Openfire cluster. There
open source alternatives for this, including the Apache web server (httpd) plus are several commercial and open source alternatives for this. For example,
<a href="http://httpd.apache.org/docs/current/mod/mod_proxy_balancer.html">mod_proxy_balancer</a> if you are using the HTTP/BOSH Openfire connector to connect to Openfire,
(if you are using the HTTP/BOSH Openfire connector). Some popular solutions include the the Apache web server (httpd) plus the corresponding proxy balancer module
(<a href="http://httpd.apache.org/docs/current/mod/mod_proxy_balancer.html">mod_proxy_balancer</a>)
could provide a workable solution. Some other popular options include the
<a href="http://www.f5.com/products/big-ip/big-ip-local-traffic-manager/overview/">F5 LTM</a> <a href="http://www.f5.com/products/big-ip/big-ip-local-traffic-manager/overview/">F5 LTM</a>
(commercial) and <a href="http://haproxy.1wt.eu/">HAProxy</a> (open source), among (commercial) and <a href="http://haproxy.1wt.eu/">HAProxy</a> (open source), among
<a href="http://en.wikipedia.org/wiki/Load_balancing_%28computing%29">many others</a>. <a href="http://en.wikipedia.org/wiki/Load_balancing_%28computing%29">many more</a>.
<p>
A simple round-robin DNS configuration can help distribute XMPP connections across multiple
Openfire servers in a cluster. While popular as a lightweight and low-cost way to provide
basic scalability, note that this approach is not considered adequate for true load balancing
nor does it provide high availability (HA) from a client perspective. If you are evaluating
these options, you can <a href="http://en.wikipedia.org/wiki/Round-robin_DNS">read more here</a>.
</p>
<h2>Upgrading the Hazelcast Plugin</h2>
<p>
The process of upgrading the Hazelcast plugin requires a few additional steps when
compared with a traditional plugin due to the cross-server dependencies within a running
cluster. Practically speaking, all the members of the cluster need to be running the
same version of the plugin to prevent various errors and data synchronization issues.
</p>
<h3>Option 1: Offline</h3>
<p><b>NOTE:</b> This upgrade procedure is neat and tidy, but will incur a brief service outage.
</p> </p>
<ol>
<li>Shut down Openfire on all servers in the cluster.</li>
<li>For the first server in the cluster, perform the following steps:</li>
<ol type="a">
<li>Remove the existing <code>plugins/hazelcast.jar</code></li>
<li>Remove (recursively) the <code>plugins/hazelcast</code> directory</li>
<li>Copy the updated <code>hazelcast.jar</code> into the <code>plugins</code> directory</li>
<li>Restart Openfire to unpack and install the updated plugin</li>
</ol>
<li>Repeat these steps for the remaining servers in the cluster.</li>
</ol>
<h3>Option 2: Online</h3>
<p><b>NOTE:</b> Using this approach you should be able to continue servicing
XMPP connections during the upgrade.
</p>
<ol>
<li>Shut down Openfire on all servers <b>except one</b>.</li>
<li>Using the Plugins page from the online server, remove the existing Hazelcast plugin.</li>
<li>Upload the new Hazelcast plugin and confirm it is installed (refresh the page if necessary)</li>
<li>Use the "Offline" steps above to upgrade and restart the remaining servers.</li>
</ol>
<h3>Option 3: Split-Brain</h3>
<p><b>NOTE:</b> Use this approach if you only have access to the Openfire console.
Note however that users may not be able to communicate with each other during the upgrade
(if they are connected to different servers).
</p>
<ol>
<li>From the Clustering page in the Openfire admin console, disable clustering. This will disable
clustering for all members of the cluster.</li>
<li>For each server, update the Hazelcast plugin using the Plugins page.</li>
<li>After upgrading the plugin on all servers, use the Clustering page to enable clustering.
This will activate clustering for all members of the cluster.</li>
</ol>
<a name="config" />
<h2>Configuration</h2> <h2>Configuration</h2>
<p> <p>
There are several configuration options built into the Hazelcast plugin There are several configuration options built into the Hazelcast plugin
...@@ -112,7 +182,7 @@ configuration file to be located outside the Openfire home directory.</li> ...@@ -112,7 +182,7 @@ configuration file to be located outside the Openfire home directory.</li>
</ol> </ol>
</p> </p>
<p> <p>
The Hazelcast plugin uses the <a href="http://www.hazelcast.com/docs/2.3/manual/single_html/#Config"> The Hazelcast plugin uses the <a href="http://www.hazelcast.com/docs/2.5/manual/single_html/#Config">
XML configuration builder</a> to initialize the cluster from the XML file described above. XML configuration builder</a> to initialize the cluster from the XML file described above.
By default the cluster members will attempt to discover each other via multicast at the By default the cluster members will attempt to discover each other via multicast at the
following location: following location:
...@@ -137,7 +207,7 @@ following alternative: ...@@ -137,7 +207,7 @@ following alternative:
&lt;/join&gt; &lt;/join&gt;
... ...
</pre> </pre>
Please refer to the <a href="http://www.hazelcast.com/docs/2.3/manual/single_html/"> Please refer to the <a href="http://www.hazelcast.com/docs/2.5/manual/single_html/">
Hazelcast reference manual</a> for more information. Hazelcast reference manual</a> for more information.
</p> </p>
</body> </body>
......
...@@ -22,11 +22,6 @@ package com.jivesoftware.openfire; ...@@ -22,11 +22,6 @@ package com.jivesoftware.openfire;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.TimerTask; import java.util.TimerTask;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
...@@ -34,18 +29,10 @@ import org.jivesoftware.openfire.cluster.ClusterManager; ...@@ -34,18 +29,10 @@ import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.container.Plugin; import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager; import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.TaskEngine; import org.jivesoftware.util.TaskEngine;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.jivesoftware.util.cache.ExternalizableUtilStrategy;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.jivesoftware.openfire.session.RemoteSessionLocator;
import com.jivesoftware.util.cache.ClusterExternalizableUtil;
import com.jivesoftware.util.cluster.ClusterPacketRouter;
/** /**
* Hazelcast clustering plugin. This implementation is based upon * Hazelcast clustering plugin. This implementation is based upon
* (and borrows heavily from) the original Openfire clustering plugin. * (and borrows heavily from) the original Openfire clustering plugin.
...@@ -54,19 +41,13 @@ import com.jivesoftware.util.cluster.ClusterPacketRouter; ...@@ -54,19 +41,13 @@ import com.jivesoftware.util.cluster.ClusterPacketRouter;
* @author Tom Evans * @author Tom Evans
* @author Matt Tucker * @author Matt Tucker
*/ */
public class HazelcastPlugin extends TimerTask implements Plugin, PropertyEventListener { public class HazelcastPlugin extends TimerTask implements Plugin {
private static Logger logger = LoggerFactory.getLogger(HazelcastPlugin.class); private static Logger logger = LoggerFactory.getLogger(HazelcastPlugin.class);
private static final long CLUSTER_STARTUP_DELAY_TIME = private static final long CLUSTER_STARTUP_DELAY_TIME =
JiveGlobals.getLongProperty("hazelcast.startup.delay.seconds", 5); JiveGlobals.getLongProperty("hazelcast.startup.delay.seconds", 5);
/**
* Keep serialization strategy the server was using before we set our strategy. We will
* restore old strategy when plugin is unloaded.
*/
private ExternalizableUtilStrategy serializationStrategy;
public void initializePlugin(PluginManager manager, File pluginDirectory) { public void initializePlugin(PluginManager manager, File pluginDirectory) {
// start cluster using a separate thread after a short delay // start cluster using a separate thread after a short delay
// this will allow other plugins to initialize during startup // this will allow other plugins to initialize during startup
...@@ -91,78 +72,14 @@ public class HazelcastPlugin extends TimerTask implements Plugin, PropertyEventL ...@@ -91,78 +72,14 @@ public class HazelcastPlugin extends TimerTask implements Plugin, PropertyEventL
logger.warn("Conflicting clustering plugins found; remove Coherence and/or Enterprise jar files"); logger.warn("Conflicting clustering plugins found; remove Coherence and/or Enterprise jar files");
throw new IllegalStateException("Clustering plugin configuration conflict (Coherence)"); throw new IllegalStateException("Clustering plugin configuration conflict (Coherence)");
} }
ClusterManager.startup();
// List for clustering setting events (e.g. enabled/disabled)
PropertyEventDispatcher.addListener(this);
if (ClusterManager.isClusteringEnabled()) {
initForClustering();
// Start up or join the cluster and initialize caches
ClusterManager.startup();
}
} }
private void initForClustering() {
// Set the serialization strategy to use for transmitting objects between node clusters
serializationStrategy = ExternalizableUtil.getInstance().getStrategy();
ExternalizableUtil.getInstance().setStrategy(new ClusterExternalizableUtil());
// Set session locator to use when in a cluster
XMPPServer.getInstance().setRemoteSessionLocator(new RemoteSessionLocator());
// Set packet router to use to deliver packets to remote cluster nodes
XMPPServer.getInstance().getRoutingTable().setRemotePacketRouter(new ClusterPacketRouter());
}
/**
* Returns the date when this release of Openfire clustering plugin was released.
*
* @return the date when this release of Openfire clustering plugin was released.
*/
public static Date getReleaseDate() {
try {
// @DATE@ should be replaced with a date with the following format: Jan 31, 2007
// Automatically set by ANT build tasks
return DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.US).parse("@DATE@");
}
catch (ParseException e) {
logger.error("Error parsing date", e);
return null;
}
}
public void destroyPlugin() { public void destroyPlugin() {
// Shutdown is initiated by XMPPServer before unloading plugins // Shutdown is initiated by XMPPServer before unloading plugins
// ClusterManager.shutdown(); if (!XMPPServer.getInstance().isShuttingDown()) {
ClusterManager.shutdown();
// Set the old serialization strategy was using before clustering was loaded }
ExternalizableUtil.getInstance().setStrategy(serializationStrategy);
// Stop listing for clustering setting events (e.g. enabled/disabled)
PropertyEventDispatcher.removeListener(this);
}
public void propertySet(String property, Map<String, Object> params) {
// Ignore
}
public void propertyDeleted(String property, Map<String, Object> params) {
// Ignore
}
public void xmlPropertySet(String property, Map<String, Object> params) {
if (ClusterManager.CLUSTER_PROPERTY_NAME.equals(property)) {
if (Boolean.parseBoolean((String) params.get("value"))) {
// Clustering was enabled
initForClustering();
}
else {
// Clustering was disabled
}
}
}
public void xmlPropertyDeleted(String property, Map<String, Object> params) {
// Do nothing
} }
} }
...@@ -585,8 +585,6 @@ public class ClusterListener implements MembershipListener, LifecycleListener { ...@@ -585,8 +585,6 @@ public class ClusterListener implements MembershipListener, LifecycleListener {
// Clean up all traces. This will set all remote sessions as unavailable // Clean up all traces. This will set all remote sessions as unavailable
List<NodeID> nodeIDs = new ArrayList<NodeID>(nodeSessions.keySet()); List<NodeID> nodeIDs = new ArrayList<NodeID>(nodeSessions.keySet());
// Revert cluster caches to local caches
CacheFactory.leftCluster();
// Trigger event. Wait until the listeners have processed the event. Caches will be populated // Trigger event. Wait until the listeners have processed the event. Caches will be populated
// again with local content. // again with local content.
ClusterManager.fireLeftCluster(); ClusterManager.fireLeftCluster();
......
...@@ -43,6 +43,8 @@ import org.jivesoftware.util.cache.Cache; ...@@ -43,6 +43,8 @@ import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactoryStrategy; import org.jivesoftware.util.cache.CacheFactoryStrategy;
import org.jivesoftware.util.cache.CacheWrapper; import org.jivesoftware.util.cache.CacheWrapper;
import org.jivesoftware.util.cache.ClusterTask; import org.jivesoftware.util.cache.ClusterTask;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.jivesoftware.util.cache.ExternalizableUtilStrategy;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -54,6 +56,8 @@ import com.hazelcast.core.Hazelcast; ...@@ -54,6 +56,8 @@ import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.Member; import com.hazelcast.core.Member;
import com.hazelcast.core.MultiTask; import com.hazelcast.core.MultiTask;
import com.jivesoftware.openfire.session.RemoteSessionLocator;
import com.jivesoftware.util.cluster.ClusterPacketRouter;
import com.jivesoftware.util.cluster.HazelcastClusterNodeInfo; import com.jivesoftware.util.cluster.HazelcastClusterNodeInfo;
/** /**
...@@ -75,6 +79,12 @@ public class ClusteredCacheFactory implements CacheFactoryStrategy { ...@@ -75,6 +79,12 @@ public class ClusteredCacheFactory implements CacheFactoryStrategy {
private static Logger logger = LoggerFactory.getLogger(ClusteredCacheFactory.class); private static Logger logger = LoggerFactory.getLogger(ClusteredCacheFactory.class);
/**
* Keep serialization strategy the server was using before we set our strategy. We will
* restore old strategy when plugin is unloaded.
*/
private ExternalizableUtilStrategy serializationStrategy;
/** /**
* Storage for cache statistics * Storage for cache statistics
*/ */
...@@ -91,6 +101,15 @@ public class ClusteredCacheFactory implements CacheFactoryStrategy { ...@@ -91,6 +101,15 @@ public class ClusteredCacheFactory implements CacheFactoryStrategy {
public boolean startCluster() { public boolean startCluster() {
state = State.starting; state = State.starting;
// Set the serialization strategy to use for transmitting objects between node clusters
serializationStrategy = ExternalizableUtil.getInstance().getStrategy();
ExternalizableUtil.getInstance().setStrategy(new ClusterExternalizableUtil());
// Set session locator to use when in a cluster
XMPPServer.getInstance().setRemoteSessionLocator(new RemoteSessionLocator());
// Set packet router to use to deliver packets to remote cluster nodes
XMPPServer.getInstance().getRoutingTable().setRemotePacketRouter(new ClusterPacketRouter());
ClassLoader oldLoader = null; ClassLoader oldLoader = null;
// Store previous class loader (in case we change it) // Store previous class loader (in case we change it)
oldLoader = Thread.currentThread().getContextClassLoader(); oldLoader = Thread.currentThread().getContextClassLoader();
...@@ -152,6 +171,13 @@ public class ClusteredCacheFactory implements CacheFactoryStrategy { ...@@ -152,6 +171,13 @@ public class ClusteredCacheFactory implements CacheFactoryStrategy {
} }
// Reset the node ID // Reset the node ID
XMPPServer.getInstance().setNodeID(null); XMPPServer.getInstance().setNodeID(null);
// Reset packet router to use to deliver packets to remote cluster nodes
XMPPServer.getInstance().getRoutingTable().setRemotePacketRouter(null);
// Reset the session locator to use
XMPPServer.getInstance().setRemoteSessionLocator(null);
// Set the old serialization strategy was using before clustering was loaded
ExternalizableUtil.getInstance().setStrategy(serializationStrategy);
} }
public Cache createCache(String name) { public Cache createCache(String name) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment