原文:http://blog.csdn.net/jinzhencs/article/details/50522322

前言:

目前我们的openfire服务器只能支撑单机2W 集群4W.(估测在线用户数已经7.8W了)

内存25G,已经去除了好友关系(我们的场景是设备控制,消息推送,无需好友关系,)

在此情况下我们需要研究出单节点25G能够支撑5W甚至更多.

并且目前感觉openfire还是有些问题的,但是无法测出来(tsung完全测不出来,500M内存在线5.6W...)

那么修改openfire哪些地方来扩容呢?

———————————————————————罪恶的分割线—————————————————————

一.cache  缓存机制

位置:org.jivesoftware.util.cache

CacheFactory:里面定义了管理控制台可以看到的所有的cache及其他的cache,其默认设置,

CacheFactoryStrategy:定义了缓存工厂的策略模式,即当没有开启集群的时候适用默认的CacheFactory,当启动集群则适用ClusterCacheFactory

场景:

为了扩大容量支撑更多用户,我们需要把几个大的cache移入redis,让redis来管理cache.

问题提出:

根据这个cache,我们可以把这个cache移植到redis里面吗?如何实现呢?后面补上

———————————————————————罪恶的分割线—————————————————————

二.建立session

1.消息接收:
package org.jivesoftware.openfire.nio[ConnectionHandler] line165:messageReceived()

2.处理包:process packet
<org.jivesoftware.openfire.net>[SocketPacketWriteHandler]line:56---process

3.上线创建session
package org.jivesoftware.openfire.nio[ConnectionHandler]-sessionOpened()

4.下线关闭session
package org.jivesoftware.openfire.nio[ConnectionHandler]-sessionClosed()

5.当一个会话 某段时间内没做任何动作,则被视为idle,每过一段时间,openfire会给这些idle的会话发送一个空包,即ping。若正常的连接,会收到返回的值,不做任何事情。若是死连接,则openfire会断开连接
org.jivesoftware.openfire.nio[ConnectionHandler]-sessionIdle()
org.jivesoftware.openfire.nio[ClientConnectionHandler]-sessionIdle()

ps:package org.jivesoftware.openfire.nio[NIOConnectionHandler] deliver即是ping发出去的包的地方,若发不出去,则关闭会话,二次发包,或者存储为离线消息
(若不要离线消息,此处可以去除代码)

6.package org.jivesoftware.openfire.net - StanzaHandler - process(Element doc)

sessionOpened这个方法里面可以看到设置了StanzaHandler去处理这个session.

而StanzaHandler里面的processMessage等可以看到是交由router去rout这个packet的.

跟进,发现它的实现方法

再跟进,这就是具体实现

———————————————————————罪恶的分割线—————————————————————

研究成果:

一.如何把cache写入redis.

cache是由DefaultCacheFactory创建或者ClusterCacheFactory创建.先说说原来集群的cache创建

位置:package org.jivesoftware.openfire.plugin.util.cache;

208行createCache()方法的return new ClusteredCache(name, hazelcast.getMap(name)); 这里返回一个cache实例

解释:

hazelcast.getMap(name):如果这个name的map不存在,则会创建,此处相当于创建一个map/获得map.意即cache存在hazelcast的 IMap 里面.

然后map.put   map.get      map.remove即可实现一系列操作

想法及问题:

按这样来看把cache移到redis实在是非常简单,只需要把createCache的map换成redis的map即可了.然后操作改一下put换成rpush等等.

但是问题是,session和cache是什么关系,还有其他的东西有用到cache吗?

———————————————————————罪恶的分割线—————————————————————

2016-01-16

问题:session和cache是什么关系,还有其他的东西有用到cache吗?

昨天花了一天算是把session打通了,完整的创建流程,遗留问题是cache和session各自的作用,为什么用了cache还用sesion.

解答:(先贴上  后面排版)

SessionManager保存了一份session的对象

然后登录的时候

package org.jivesoftware.openfire.handler - IQAuthHandler - 190行登录

把这个session传入,之后把session做成sessionInfo存入cache

算起来两个地方保存了session,1是sessionmanager 2是cache

之后sessionManager管理的又是LocalsessionManager和

转入到这里面去拿
1.
//Auth前保存的  之后看auth完了把这个清空不?   (最下面的总结可以看到 清楚了的!猜想正确)
getPreAuthenticatedSessions(){
}

这是声明:即这里存的就是session对象了(保存的认证Auth之前的)

private Map<String, LocalClientSession> preAuthenticatedSessions = new ConcurrentHashMap<String, LocalClientSession>();  

或者
2.
routingTable.getClientRoute(from);

位于
package org.jivesoftware.openfire.spi - RoutingTableImpl - 686行

这里拿的是package org.jivesoftware.openfire.spi - LocalRoutingTable

 Map<String, RoutableChannelHandler> routes = new ConcurrentHashMap<String, RoutableChannelHandler>();

这里面存的是package org.jivesoftware.openfire.spi - RoutingTableImpl-153行

    public boolean addClientRoute(JID route, LocalClientSession destination) {
boolean added;
boolean available = destination.getPresence().isAvailable();
localRoutingTable.addRoute(route.toString(), destination); //这存进去的ClientSession。
**********************
usersSessions.put(route.toBareJID(), Arrays.asList(route.toString())); //// 把session数据加入UserCache和UserSession

总结:

最主要就是这一段:完成了把session加入cache和map里面

package org.jivesoftware.openfire - SessionManager   -  550 行

public void addSession(LocalClientSession session) {
// Add session to the routing table (routing table will know session is not available yet)
routingTable.addClientRoute(session.getAddress(), session);
// Remove the pre-Authenticated session but remember to use the temporary ID as the key
localSessionManager.getPreAuthenticatedSessions().remove(session.getStreamID().toString());
SessionEventDispatcher.EventType event = session.getAuthToken().isAnonymous() ?
SessionEventDispatcher.EventType.anonymous_session_created :
SessionEventDispatcher.EventType.session_created;
// Fire session created event.
SessionEventDispatcher.dispatchEvent(session, event);
if (ClusterManager.isClusteringStarted()) {
// Track information about the session and share it with other cluster nodes
sessionInfoCache.put(session.getAddress().toString(), new ClientSessionInfo(session));
}
}

完整加入会话流程是:

1.在auth完成前,先加入PreAuthenticatedSessions中

2.xmpp来回几步,到auth成功前一步,客户端发来xx请求的时候,发来的请求被 IQHandler - process(packet) 接收

3.然后被 IQAuthHandler - handlerIQ(IQ packet) 接管, 它先从sessionManager取到之前的preAuthenticatedSessions里面的客户端session,

4. 然后 判断         if (JiveGlobals.getBooleanProperty("xmpp.auth.iqauth",true)) {    说明是auth请求

5.然后在preAuthenticatedSessions里面的判断session的状态     if (session.getStatus() == Session.STATUS_AUTHENTICATED)       
  发现还没有完成auth , 则 执行login

                        else {
// it is an auth attempt
response = login(username, query, packet, password, session, digest);
resourceBound = session.getStatus() == Session.STATUS_AUTHENTICATED;
}

6.然后进入login方法,一系列判断后,走到最后面的        session.setAuthToken(token, resource);

7.这个方法先把preAuthenticatedSessions的状态设置为setStatus(Session.STATUS_AUTHENTICATED); 然后就执行上述最重要的一步  sessionManager.addSession(this);

8.先把新的session加入route里面,          Map<String, RoutableChannelHandler> routes = new ConcurrentHashMap<String, RoutableChannelHandler>();

9.然后从preSession里面移除旧的              localSessionManager.getPreAuthenticatedSessions().remove(session.getStreamID().toString());

10.然后看集群是否启动来决定是否加入sessionInfoCache

        if (ClusterManager.isClusteringStarted()) {
// Track information about the session and share it with other cluster nodes
sessionInfoCache.put(session.getAddress().toString(), new ClientSessionInfo(session));
}

11.然后创建一个返回的IQ

12.完毕

———————————————————————罪恶的分割线—————————————————————

2016-01-18:

研究成果:

今天考虑了下如何把openfire的cache和session对象 保存入redis,发现redis只支持存储序列化的byte或者一些基本类型,

之前想到了序列化,但是session对象是没法序列化的,hazelcast集群插件实现同步(网络传输)都是自己手写的序列化方法实现的序列化.我就不用这种方法了.思来想去想到了一个好方法,JSON来实现序列化!

然后验证了下JSON序列化对象的可行性,发现完全可以!

可以看到,redis已经成功存入了.

并且被序列化的Man不仅仅是个JavaBean,我还给予了它一些方法,以及私有参数引入其他的对象.

都完全没问题.所以初步判定cache和session通过JSON方式存储读取是完全可行的.

———————————————————————罪恶的分割线—————————————————————

2016-01-19

今早上思考了下,存储整个DefaultCache对象代价太高了,那样效率估计比原来还低.因为每次取都需要把整个对象取出然后得到它的map,进而得到相应的键值.

于是决定只存储Defaultcache里面的Map,这个才是主要影响用户量及性能的.并且能和redis的map无缝对接.

如图:

HMSET CacheName JID SessionInfoOrCacheInfo 是完全OK的.

现在的问题在于DefaultCache里面的map

/**
* The map the keys and values are stored in.
*/
protected Map<K, DefaultCache.CacheObject<V>> map;

第一个参数是泛型,第二个是cacheObject.

第二个比较好解决,就是个JavaBean.

现在难点在于第一个,接下来攻克第一个泛型JSON序列化的问题!Cache就算打通了.之后再搞session.

openfire源码解读之将cache和session对象移入redis以提升性能的更多相关文章

  1. openfire源码解读--用户登录

    根据xmpp协议 客户端发送: <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>XXXXXXXXXXXXX ...

  2. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  3. SDWebImage源码解读之SDWebImageCache(上)

    第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...

  4. SDWebImage源码解读之SDWebImageCache(下)

    第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...

  5. AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking

    我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...

  6. AFNetworking 3.0 源码解读(八)之 AFImageDownloader

    AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...

  7. jQuery.Callbacks 源码解读二

    一.参数标记 /* * once: 确保回调列表仅只fire一次 * unique: 在执行add操作中,确保回调列表中不存在重复的回调 * stopOnFalse: 当执行回调返回值为false,则 ...

  8. String、StringBuffer、StringBuilder源码解读

    序 好长时间没有认真写博客了,过去的一年挺忙的.负责过数据库.线上运维环境.写代码.Code review等等东西挺多. 学习了不少多方面的东西,不过还是需要回归实际.加强内功,方能扛鼎. 去年学习M ...

  9. 第二十五课:jQuery.event.trigger的源码解读

    本课主要来讲解jQuery.event.trigger的源码解读. trigger = function(event, data, elem, onlyHandlers){ if(elem & ...

随机推荐

  1. iOS如何隐藏状态栏,包括网络标志、时间标志、电池标志等

    我们在开发App的时候,在遇到有状态栏时,NavigationBar上面的按钮,是极难点击的,所以这个时候,最好我们能够人为的隐藏掉状态栏. 如果一直需要隐藏的话,直接在info.plist里面,添加 ...

  2. P4305 [JLOI2011]不重复数字

    题目描述 给出N个数,要求把其中重复的去掉,只保留第一次出现的数. 例如,给出的数为1 2 18 3 3 19 2 3 6 5 4,其中2和3有重复,去除后的结果为1 2 18 3 19 6 5 4. ...

  3. NAT64与DNS64基本原理概述

    NAT64与DNS64基本原理概述 1.NAT64与DNS64背景     在IPv6网络的发展过程中,面临最大的问题应该是IPv6与IPv4的不兼容性,因此无法实现二种不兼容网络之间的互访.为了实现 ...

  4. [SDOI2017][bzoj4817] 树点涂色 [LCT+线段树]

    题面 传送门 思路 $LCT$ 我们发现,这个1操作,好像非常像$LCT$里面的$Access$啊~ 那么我们尝试把$Access$操作魔改成本题中的涂色 我们令$LCT$中的每一个$splay$链代 ...

  5. 测试计划驱动开发模式 TPDD:一种比 TDD 更友好的开发模式

    相信大部分开发团队都在使用TDD,并且还有很多开发团队都 对外声明 在使用 TDD 开发模式. 之所以说是“对外声明”,是因为很多开发团队虽然号称使用的是 TDD 开发模式,实际开发过程中却无法满足 ...

  6. POJ 1064 Cable master | 二分+精度

    题目: 给n个长度为l[i](浮点数)的绳子,要分成k份相同长度的 问最多多长 题解: 二分长度,控制循环次数来控制精度,输出也要控制精度<wa了好多次> #include<cstd ...

  7. 汕头市队赛 SRM 07 C 整洁的麻将桌

    C 整洁的麻将桌 SRM 07 背景&&描述 天才麻将少女KPM立志要在日麻界闯出一番名堂.     KPM上周双打了n场麻将,但她这次没控分,而且因为是全民参与的麻将大赛,所以她的名 ...

  8. 理想中的SQL语句条件拼接方式

    背景 Orm用过一些,但处理增删改上面做的都不错.但是查询上跟我想要的效果总是差了一点.我想要的效果则是这样,基于某种命名规则进行传参,后台解析器知道命名规则即可知道它要查询什么样的数据. 谈谈我之前 ...

  9. 开源免费的C/C++网络库(c/c++ sockets library)(转)

    原文转自 http://blog.csdn.net/weiwangchao_/article/details/8730199 (1)ACE 庞大.复杂,适合大型项目.开源.免费,不依赖第三方库,支持跨 ...

  10. [leetcode]Convert Sorted Array to Binary Search Tre

    排好序的... 中间是root , root左边是left,root右边是right 递归建树. /** * Definition for binary tree * struct TreeNode ...