集群
Openfire的给集群提供了多种方案。一种是基于Hazelcast插件,还有基于Oracle的coherence插件。
Oracle的coherence插件中文开发文档:http://download.csdn.net/detail/huwenfeng_2011/8423519
Linux集群配置
一、修改配置文件
/etc/hosts文件
openfire1 192.168.2.104
openfire2192.168.2.240
每台主机都需要配置
二、添加jar包
将coherence.jar、coherence-work.jar、tangosol.jar添加到lib目录
将clustering.jar放到plugins下面
Hazelcast Clustering Plugin
Hazelcast插件添加支持运行多个冗余的Openfire服务器集群中的一起。作为群集运行的Openfire,您可以分发的几个服务器之间的连接负载,同时还提供故障转移服务器发生故障的事件。这个插件是更换为原来的Openfire收费集群插件,使用开源的Hazelcast数据分布框架代替昂贵的专有第三方产品。
安装
创建openfire集群,应该有至少两个的Openfire服务器,每个服务器必须安装Hazelcast插件。要安装Hazelcast ,只需将hazelcast.jar拖放到 $OPENFIRE_HOME/plugins目录下,和其他安装的插件一样。您也可以使用插件的页面,从管理控制台安装插件。需要注意的是必须配置一个集群中的所有服务器共享一个单一的外部数据库(而不是嵌入式DB ) 。
Openfire的启动/初始化过程在默认情况下,服务器会发现对方UDP (组播)通过一个可配置的IP地址和端口的数据包交换。但是,请注意,许多其他初始化选项是可用的,并且可以使用,如果你的网络不支持组播通信(见下面的配置) 。
Hazelcast插件后已部署到每个服务器,在控制管理台上选择系统管理找到clustering界面,点击单选按钮。你只需要一次启用集群;这项更改会自动传播到其他服务器。刷新clustering页后,你将能看到所有服务器已成功加入集群。
需要注意的是Hazelcast与早期的clustering的插件(clustering.jar and enterprise.jar)是相互排斥的。 Hazelcast安装到您的Openfire服务器(S)之前,你将需要删除的任何现有旧的clutering 插件。
随着群集的建立和运行,现在你将需要某种形式的负载平衡器您的Openfire集群成员之间分发连接负载。有一些商业和开源替代品,例如,如果您使用的是HTTP/BOSH Openfire的连接器连接到Openfire的Apache Web服务器(httpd)加上相应的代理平衡器模块(mod_proxy_balancer)可以提供一个可行的解决方案。其他一些流行的选择包括F5 LTM (商业)和HAProxy的(开源)等等。
一个简单的DNS轮循配置可以帮助分发XMPP连接跨多个Openfire的服务器集群中的。虽然作为一个轻量级的和低成本的方式提供基本的可扩展性,注意,这种做法是不考虑足够的真正的负载平衡,也不提供高可用性(HA) ,从客户的角度受欢迎。如果您正在评估这些选项,你可以在这里阅读更多。
Hazelcast插件升级
Hazelcast插件升级比较复杂,因为集群中的其他主机还依附着以前较老的版本。在openfire其中所有的主机应该都使用同一个版本的Hazelecast插件,以免出来各种错误或者数据同步问题。
选项1:offline
注: 这个升级过程比较纯粹不会影响其他业务、数据,但是他需要产生暂短的服务终止
1、关闭openfire集群上的所有服务器
2、每台服务器都应该做如下操作
a) 删除先有插件hazelcast.jar
b) 删除插件目录 plugins/hazelcasr
c) 将新的hazelcasr.jar插件复制到 plugins目录
d) 加载插件,重启openfire
3、在集群的其他插件重复2的操作
选项2:online
注: 这个升级方法,openfire能够继续xmpp服务
1、在openfire集群上留一主机不需要关闭,关闭其他的主机
2、重复选项1的操作。
选项3:Split-Brain
注: 如果你能访问到openfire控制台,则使用这种方法。如果他们连接到不同的服务器, 在升级过程中,用户之间可能不会通信。
1、从clusering页面上Openfire管理控制台,禁用集群。这将禁用集群的集群的所有成员。
对于每个服务器,更新Hazelcast插件使用插件页面。
2、升级后的所有服务器上的插件,使用聚类页启用集群。这将激活集群集群的所有成员。
配置
Hazelcast插件内置到Openfire的系统属性有几个配置选项:
1、 hazelcast.startup.delay.seconds(5):等待的秒数发射前Hazelcast插件。这允许的 Openfire之前部署任何其他插件初始化集群缓存等。
2、 hazelcast.startup.retry.count(1):重试次数如果集群初始化启动失败,在第一次尝试。
3、 hazelcast.startup.retry.seconds(10):等待后续尝试启动群集之间的秒数。
4、 hazelcast.max.execution.seconds(30):当运行一个集群成员之间的同步任务的最长等待 时间。
5、 hazelcast.config.xml.filename(hazelcast缓存-config.xml)中:Hazelcast配置文件的名 称。通过覆盖这个值,你可以很容易地Hazelcast插件/班/目录中安装自定义缓存配置文 件,在命名的目录通过hazelcast.config.xml.directory的属性(如下所述),或在classpath 中您自己的自定义插件。
6、 hazelcast.config.xml.directory({OPENFIRE_HOME}/ conf目录下):目录将被添加到该 插件的类路径中。这使得Hazelcast一个自定义配置文件来Openfire的主目录之外。
7、 hazelcast.config.jmx.enabled(false):Hazelcast集群如果已启用JMX通过Openfire管理 控制台启用JMX支持。有关更多信息,请参阅Hazelcast JMX文档。...
Hazelcast插件使用XML配置生成器,从XML文件中上述初始化集群。默认情况下,集群成员试图通过多播发现对方在以下位置:
IP地址:224.2.2.3
端口:54327
注意,这些值可以被覆盖的plugin's /classes/hazelcast-cache-config.xml(通过组播组和组播端口元素)。许多其它的初始化和发现存在Hazelcast配置文档如上所述记录。例如,成立一个双节点群集使用众所周知的DNS名称/端口值,请尝试以下选择:
- <network>
- <port auto-increment="true">5701</port>
- <join>
- <multicast enabled="false"/>
- <tcp-ip enabled="true">
- <hostname>192.169.1.240:5701</hostname>
- <hostname>192.169.1.250:5701</hostname>
- <hostname>192.169.1.120:5701</hostname>
- <hostname>192.169.1.104:5701</hostname>
- </tcp-ip>
- <aws enabled="false"/>
- </join>
- <interfaces enabled="false"/>
- <ssl enabled="false" />
- <socket-interceptor enabled="false" />
- <symmetric-encryption enabled="false">
- <!--
- encryption algorithm such as
- DES/ECB/PKCS5Padding,
- PBEWithMD5AndDES,
- AES/CBC/PKCS5Padding,
- Blowfish,
- DESede
- -->
- <algorithm>PBEWithMD5AndDES</algorithm>
- <!-- salt value to use when generating the secret key -->
- <salt>thesalt</salt>
- <!-- pass phrase to use when generating the secret key -->
- <password>thepass</password>
- <!-- iteration count to use when generating the secret key -->
- <iteration-count>19</iteration-count>
- </symmetric-encryption>
- <asymmetric-encryption enabled="false">
- <!-- encryption algorithm -->
- <algorithm>RSA/NONE/PKCS1PADDING</algorithm>
- <!-- private key password -->
- <keyPassword>thekeypass</keyPassword>
- <!-- private key alias -->
- <keyAlias>local</keyAlias>
- <!-- key store type -->
- <storeType>JKS</storeType>
- <!-- key store password -->
- <storePassword>thestorepass</storePassword>
- <!-- path to the key store -->
- <storePath>keystore</storePath>
- </asymmetric-encryption>
- </network>
进入控制台管理页面:
在这里选择启动,并保存设置。然后等待几分钟,会出现集群内的主机
接下来点击一个节点,查看里面的内容
源码分析
在openfire的核心原理集成的缓存插件CacheFactory中方法:
isClusteringAvailable()
该方法返回true,如果群集安装,可以使用此JVM加入集群。 false值可能意味着,要么集群支持是不可用或许可证不允许有超过1群集节点。
- public static boolean isClusteringAvailable() {
- 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);
- }
加入集群的时候会先判断该主机是否已经加入集群,这里就是要判断该主机是否加入了集群,如果加入集群的话则会重载分布式缓存。
- public static synchronized void startup() {
- if (isClusteringEnabled() && !isClusteringStarted()) {
- initEventDispatcher();
- CacheFactory.startClustering();
- }
- }
上面方法中有两个步骤:
1)initEventDispatcher();调度线程实例化和启动群集事件
2)CacheFactory.startClustering();启动集群
- private static void initEventDispatcher() {
- if (dispatcher == null || !dispatcher.isAlive()) {
- dispatcher = new Thread("ClusterManager events dispatcher") {
- @Override
- public void run() {
- // 当集群被禁止,退出线程
- while (ClusterManager.isClusteringEnabled()) {
- try {
- Event event = events.take();
- EventType eventType = event.getType();
- // 更新缓存的时候,让CacheFacory最先得到这个事件
- if (event.getNodeID() == null) {
- // Replace standalone caches with clustered caches and migrate data
- if (eventType == EventType.joined_cluster) {
- CacheFactory.joinedCluster();
- } else if (eventType == EventType.left_cluster) {
- CacheFactory.leftCluster();
- }
- }
- // Now notify rest of the listeners
- for (ClusterEventListener listener : listeners) {
- try {
- switch (eventType) {
- case joined_cluster: {
- if (event.getNodeID() == null) {
- listener.joinedCluster();
- }
- else {
- listener.joinedCluster(event.getNodeID());
- }
- break;
- }
- case left_cluster: {
- if (event.getNodeID() == null) {
- listener.leftCluster();
- }
- else {
- listener.leftCluster(event.getNodeID());
- }
- break;
- }
- case marked_senior_cluster_member: {
- listener.markedAsSeniorClusterMember();
- break;
- }
- default:
- break;
- }
- }
- catch (Exception e) {
- Log.error(e.getMessage(), e);
- }
- }
- // Mark event as processed
- event.setProcessed(true);
- } catch (Exception e) {
- Log.warn(e.getMessage(), e);
- }
- }
- }
- };
- dispatcher.setDaemon(true);
- dispatcher.start();
- }
- }