第十二课:Openfire核心模块之的缓存系统的实现(一)

阅读:10209

1、为什么要使用缓存系统

2、Openfire中缓存的使用

3、缓存系统的回收机制

一、题外话:女孩们,您们辛苦了

1、女孩适合这个职业吗?

亲爱的兄弟姐妹们,大家好,有姐妹们吗?应该没有吧,哈哈。

如果有,那么我只有说,您们辛苦了,因为技术这个职业确实有点影响您们的内分泌。如果没有太大的理想和抱负,有条件转一下行,就转吧。毕竟社会压力太大了,女孩子,不适合。

当然,个别女孩子也是适合的,在国外IT行业的女高管也多得是,我们并不排除另类。

此处有点悲情,没有太多的正能量,但确实是我这些年来最真实的感受。

我曾经有一个女朋友了,大学前5年学习美术,后来进了清华美院。毕业后,进入百度做UCD,工作很辛苦,经常加班到10点才回家,当然,我也差不多10点才回家。大家除了晚上睡觉外,就很少有交流了。

因为,工作压力很大,她的脾气也越来越差,当然,我也越来越差,终于有一天,大家不再想吵架了,分手了。

我很喜欢她的专业,但是不喜欢她和我同样的职业,所以,我再也不找同行做女朋友了。

2年后,她也熬不住了,因为在百度的经验,她顺利的进入了她家乡的一所大学任网络新媒体老师。从她的朋友圈中,我看得出来,现在的她很开心。如果有可能,我愿意和现在的她在一起,不过往事已如烟。

不知道,大家从这个故事中有没有得到启发,不管有没有,这仅仅是我的人生经历而已。感谢阅读。

2、然而男孩,应该更认真,学习原理是最重要的。

当然,接下来的话就是给男生说的了,如果你是吃技术这碗饭的,那么一定要认真学习,认真思考,技术没有顶端,你只有不断修炼内力,方可成功。说直白一点,您的技术可以和金钱挂钩,而家庭、爱人需要金钱。

如果您正在开发即时通讯软件,仅仅会用环信、融云之类的API是远远不够的。您仅仅只会用它,除了这点,您还会什么呢?懂即时通讯的原理和实现细节吗?如果您现在问自己这句话,您的内心会告诉您,“不会”。

哈哈,大多数同学是不懂的,那么您需要学一下,我们的课程。学习本网站的openfire是一个修炼内力的过程,因为,我们会从设计原理中,启发大家。

ok,废话,不多说,开始吧。

二、缓存有些高大上,看看openfire如何优雅的实现

在openfire的初级课程中,一开始就来说说openfire的缓存机制,似乎太高大上了。是不考虑初学者的节奏吗?

不是的,我们仔细思考了一下,如果仅仅讲一些注册、发送消息、发送图片、文件、语音之类的知识,就不够吸引人了。初级课程还是要有一些吸引人的干货,以利于大家更愿意去学习中级高级课程嘛。我们乐于去推荐中高级课程,是因为,它们确实有很多值得深入学习的干货。

所以,即使你现在还不知道怎么发送文件,图片,也没关系,因为这些东西,我们在后面的课程中迟早会讲。

现在就静下心来,和我们一起学习openfire的缓存设计吧。

三、openfire缓存概要

1、openfire中的缓存

openfire缓存设计在org.jivesoftware.util.cache这个包中,有10几个类协同来实现缓存机制。在必要的时候,您甚至可以将这个包拷贝到您的其他项目中,稍微修改一下就可以使用

首先要明确什么是缓存(Cache),这个概念好像不用解释,大家也知道是什么。但是这里,我还是不厌其烦的给大家一个解释吧。

缓存:当您从数据库、网络或者以其他途径获得数据很慢时,可以直接将结果缓存在内存中,以便以后快速获取。这部分内存就叫缓存,管理缓存的系统就叫缓存系统。

2、缓存的管理的重点----当内存不足时

当缓存占用大量内存,导致内存不够用时,业界一般有两种处理方式。

第一种方式:将长期没用的缓存,从内存中删掉。长期没有使用的缓存,放在内存中也没有用,先删掉释放内存最好。有得同学可能兴趣来了,要反问一下,怎么确定哪些缓存长期没有用,这是另一个话题,大家可以学习一下LRU算法,详细的内容我们后面会阐述。

第二种方式:将内存的不常用数据持久化到硬盘中,最好是固态硬盘,现在的固态硬盘,每s能够400MB。没有任何优化的情况下,10s钟就可以将4G内存的数据全部写到硬盘,所以速度是很快的。

有个同学,看了课程后,问我,4G的缓存数据写入硬盘也需要10s啊,岂不是很慢,会不会影响程序的性能。

我们这里来分析一下:首先,不会有缓存系统设计成一次持久化4G的数据到硬盘中,缓存系统会找机会将不常用的数据,一点点的写入到硬盘中,所以不会出现一次写4G的数据,一般来说,一次写1M的数据的情况都非常少,大部分可能就是几百K。所以,几百K的数据,只需要几微妙的时间,完全可以忽略不计。而且,大部分情况下,缓存都是从内存中,直接读的,不需要读写硬盘,所以效率更高。所以,上面那位同学的担心就大可不必了。

哎,是不是一个很屌丝的解释,大家尽力吐槽吧。

四、openfire缓存系统的总体架构

1、大型系统为什么要使用缓存

即时通讯服务器不可避免要出现高并发的情况,例如获得一个2000人的组列表,这个列表成员的信息需要保存在缓冲中。 我们首先来分析不保存在缓存中会怎么样?如果不保存在缓存中,就需要从数据库去取数据,从数据库取数据速度很慢,会影响消息的群发,对消息服务器性能产生很大的影响。

所以,我们需要将这些数据缓存起来。好了,现在静下心来,想一想,你曾经开发过的任何程序,哪些需要缓存来优化一下呢?如果您已经想到了哪些需要优化,那么您已经进步很多了。

2、openfire缓存系统的总体架构

下面,我们来看看openfire缓存系统总体架构,如下图所示:

从图中,我们会发现,缓存系统可能被openfire的其他模块所调用,例如各个子模块(登录、网络、权限等)、插件、组件等。

Openfire中很多地方有高速读写的需求,所以需要用到缓存系统。Ok,这就是openfire关于缓存的总体架构,在这个架构中,缓存作为基础模块,供其他模块使用。

六、缓存系统在内存中的存储结构

为了更容易的理解openfire缓存,我们要讲一下缓存在内存中的存储。要明白缓存在内存中的数据存储结构,我这里要先给大家几个术语,这几个术语,是我自己定义的,不能在任何书籍中找到,所以大家先理解一下再说:

缓存对象:需要缓存到内存中的对象,例如一个Long,一个String,或者一个Object以及自己定义的对象。

缓存块:一个缓存对象的集合,缓存块有一个名字,例如"UserCache"。里面以键值对的方式存放很多缓存对象。

总结起来,缓存块中包含很多缓存对象。每个缓存块有一个名字,如果设定了缓存块的大小,那么缓存块也有大小。

还有一点要注意,缓存系统中有很多缓存块。他们三者的关系是,如下图所示,希望您能明白。

如果您对上面的讲解不是很懂,那么我建议您打开org.jivesoftware.util.cache这个包看一下。

七、org.jivesoftware.util.cache包下面各个类之间的关系分析

在介绍缓存系统具体的实现前,我们先看一下org.jivesoftware.util.cache包下的几个类的关系,如下图所示:

Cache接口是缓存块的接口,所有的缓存块对象都要实现这个接口。

Cache接口有2个实现类,分别是DefaultCache和CacheWrapper。它们都是缓存块类的具体实现类,这里我们用得较多的是DefaultCache。

CacheFactory类是管理缓存的工厂类,缓存的创建,修改,删除都可以使用这个类完成。

CacheFactory类有一个成员变量cacheFactoryStrategy,负责缓存的分配回收策略。

CacheFactoryStrategy是缓存的分配回收策略接口,这个接口默认的实现类是DefaultLocalCacheStrategy类,DefaultLocalCacheStrategy这个类负责缓存需要的内存的管理(创建、回收等)。

CacheSizes是缓存的工具类,用于计算每个缓存对象的字节大小。

八、从简单开始,怎么使用缓存,CacheFactory的使用

CacheFactory是实现缓存的一个非常重要的类,您可以在org.jivesoftware.util.cache包下找到它。它是一个工厂类,什么是工厂类,如果不明白,学学设计模式吧。不过不学习也没关系,因为您可以跟着我们先把即时通讯的原理学好,再去补知识。

CacheFactory在openfire中被广泛使用到,基本上使用缓存的地方,就会用到它。现在,请打开您的CacheFactory.java文件,我们一起来看看怎么使用。

1、创建缓存对象函数(createCache)

创建缓存使用createCache函数,其构造函数如下:

public static synchronized <T extends Cache> T createCache(String name)
        

参数name,表示缓存的名字。

返回值是一个继承Cache接口的类。Cache是什么,它就是上面讲解的缓存块对象。关于createCache的详细说明,我们接下来再说,当然如果您学习文字课程很费力,可以看后面中高级的视频课程。

下面是一个例子,表示创建一个名字为“POP3 Authentication”的缓存对象:

String cacheName = "POP3 Authentication";
authCache = CacheFactory.createCache(cacheName);

2、CacheFactory中几个成员变量

一般来说,关于CacheFactory类,只需要掌握createCache就足够了。其他的操作,都在创建缓存后,返回的Cache中进行操作。

另外,在CacheFactory中,有几个成员变量,他们分别定义为:

private static final Map<String, String> cacheNames = new HashMap<String, String>();
private static final Map<String, Long> cacheProps = new HashMap<String, Long>();
private static Map<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

下面分别对这几个变量进行简要解释。

3、cacheNames 中存放的啥玩意

cacheNames变量,这个存放所有缓存块的名字。

4、cacheProps存放openfire常用属性

cacheProps中存放的是系统常用属性。Openfire启动的时候,会为一些常用属性设置缓存。例如要限制openfire传输文件的大小,可以这样存:

cacheProps.put("cache.fileTransfer.size", 128 * 1024l);
 

这里定义了文件传输最大只能是128K。

又如,我们可以定义每个人最多可以存储的离线消息的大小,可以如下设置:

cacheProps.put("cache.offlinemessage.size", 100 * 1024l);

这里定义了每个人离线消息最大是100K。当openfire存离线的时候,就会取到当前这个人的离线消息,已经使用了多少空间,然后和cacheProps中的缓存数据进行比较。如果已经超过了这个值,那么就允许存离线消息了。因为这个数据经常用到,所以存到缓存中。

5、caches存放Cache对象

Caches中存放Cache对象。

好了,CacheFactory中三个重要的成员变量就说到这里了。

九、给缓存起一个名字

需要openfire缓存的数据非常多,例如用户在线状态需要缓存,用户的连接状态也可能需要缓存。由此看来,需要缓存的数据有可能是非常多的。为了避免混乱,我们给每一类缓存命一个名字。例如用户的会话连接可以用这个名字"Connection Managers Sessions"来表示。

好了,关于缓存的名字我们就将这么多知识就够了。

十、CacheFactory中的setMaxSizeProperty函数,设置缓存的大小

setMaxSizeProperty函数可以设置某个名字的缓存的大小,这个函数的原型是:

public static void setMaxSizeProperty(String cacheName, long size)
            

第一个参数cacheName表示缓存的名字。

第二个参数size表示这个缓存最大可以占用的内存空间。

详细代码如下:

public static void setMaxSizeProperty(String cacheName, long size) {
    cacheName = cacheName.replaceAll(" ", "");
    JiveGlobals.setProperty("cache." + cacheName + ".size", Long.toString(size));
}

通过系统属性设定了名为"cache." + cacheName + ".size"的缓存的最大大小。这个大小会在有缓存加入缓存块的时候,进行验证,看是否超过了这个缓存块的大小。

最后,我们来图文并茂一下,下图是openfire中的缓存摘要。说明了缓存的空间占用情况。

上图中,最大空间:表示缓存可以占用的最大内存大小。无限制,表示可以占用任意大小的内存。

当前空间:表示某个缓存已经使用了多少空间。

使用百分比:表示使用了最大空间百分之多少的缓存。

效果:表示缓存的命中情况。例如缓存中的数据,经常被访问,那么这个值比较大。

十一、CacheFactory中的hasMaxSizeFromProperty函数表示某个缓存是否有最大容量限制

hasMaxSizeFromProperty函数表示某个缓存是否有最大容量限制,它的原型如下:

public static boolean hasMaxSizeFromProperty(String cacheName)

如果cacheName这个缓存有最大容量的限制,那么返回true。这里提一个问题,为什么缓存需要大小限制呢? 写到这里的时候,我仿佛听到一个同学说,“老师你是不是脑残了,如果缓存没有大小限制,那么不是任意大小的数据都可以存到内存中,那样内存很快就会用完的。”

哦,原来如此,所以,缓存需要大小限制。是不是您已经和openfire的设计者一样聪慧了,知道为什么要这样去设计缓存系统了。 最后,要注意的是,当调用了setMaxSizeProperty这个函数去设置某个缓存的大小,那么hasMaxSizeFromProperty应该返回true。hasMaxSizeFromProperty函数的代码如下:

public static boolean hasMaxSizeFromProperty(String cacheName) {
    return hasCacheProperty(cacheName, ".size");
}

十二、缓存的生命周期

openfire每一个缓存对象都有一个生命周期,默认情况下最大是6个小时。如果6个小时内,这个缓存对象没有用到过,那么就回收这个缓存对象。

1、一个女生的疑问

写到这里,我仿佛又听到一个柔弱又羞答答的女孩问:“老师,为什么缓存要有生命周期,而且是6小时啊,不是24小时,或者1024小时呢?” 还没有等我回答,我仿佛又听到一个帅哥说:“妹子,如果人没有寿命,是不是会长生不老。如果长生不老,地球会不会有一天因为人太多而爆炸。给每个人设定只能活100岁,是不是可以给新人留一点生存空间。至于为什么是6小时,而不是24小时,我觉得这只是一个经验值,你设置成8小时,也应该对缓存系统的垃圾回收,影响也不大。”。

听了这位帅哥的解释,不知道您懂了没有,我好像懂了,虽然他是解释给那位女生听的。

2、getMaxCacheLifetime函数获得最大生命周期

Ok,接下来,如果要获得某个缓存的最大生命周期,使用getMaxCacheLifetime函数,它的代码如下:

public static long getMaxCacheLifetime(String cacheName) {
    return getCacheProperty(cacheName, ".maxLifetime", DEFAULT_MAX_CACHE_LIFETIME);
}

这里的DEFAULT_MAX_CACHE_LIFETIME定义为6小时。

十三、获取系统中所有的缓存

如果要获得系统中所有的缓存,那么可以使用getAllCaches函数,这个函数的源码如下:

public static Cache[] getAllCaches() {
    List<Cache> values = new ArrayList<Cache>();
    // 从caches中取出所有的cache,放到一个list中
    for (Cache cache : caches.values()) {
        values.add(cache);
    }
    // 将list转成一个数组
    return values.toArray(new Cache[values.size()]);
}

这个缓存将系统中的所有缓存块对象返回。

大多数情况下,openfire不需要获取系统中的所有缓存对象,因为没有多少卵用。但是后台实现的缓存摘要页面,就使用getAllCaches函数去获取所有的缓存对象。如下图所示:

十四、未完待续

未完待续,我们下节课再见

提问或评论

登陆后才可留言或提问哦:) 登陆 | 注册 登陆后请返回本课提问
用户名
密   码
验证码