关于缓存的思考

xmuzyu 09-04-24
                   

在评价一个系统的时候,性能指标是很重要的,那么在当前J2EE的系统开发当中,如何来提高系统的性能呢?我觉得应该从对象管理入手,从对象的生命周期开始。虽然大家可能会说,Java有垃圾收集器,我们的对象的生命周期不需要我们自己管理,但是如果要是真的过分依赖java语言本身的特性,那么我相信,系统的性能肯定好不到哪去。

下面我主要说一下对于缓存的理解。在说 缓存之前,我不得不说一下面向对象的设计,可能有些人认为,为什么 缓存会与面向对象的设计扯上关系,其实这就是 缓存的关键。首先设想一下,如果开发系统的过程中,都是采用面向过程,面向数据库的思维编程,每一次业务操作,我们都是调用通过数据库操作来完成,这其实就是POEAA中的事务脚本,只适合一些简单的系统的开发,或者一个项目中,比较简单模块的开发,对于复杂的模块,更好的方式就是采用面向对象的方法来进行开发。

好了,说到了面向对象的设计问题,至于这个问题已经有很多书籍以及很多人讨论了很多年了,就我个人来说,我觉得采用DDD建模是目前比较适合的一种方式。DDD中涉及到得每种模式或者说是每一种模型元素对于 对象设计来说都是很重要的,而对象模型的设计又对缓存的设计非常重要,下面我说说我的想法:

首先我说一下关于聚合的问题,为什么说聚合对于 缓存非常重要呢?这其实涉及到了一种控制访问的问题,一个聚合根控制了对整个聚合的访问,要想访问聚合里的对象必须要通过聚合的根。 好了,我们以一个实例来说话,比如jdon中的Forum对象的设计,在jdon中有ForumState对象,Forum对象是聚合的根,是一个实体模型,而ForumState是一个值对象,并且是属于Forum这个聚合根的子对象,我们把ForumState对象从Forum对象分离出来,好处主要有两个,从事务的角度来说,当我们更新ForumState对象的时候,不用锁住Forum对象,从 缓存设计的角度来说,当我们更新ForumState对象的时候不用刷新Forum对象的 缓存,因为Forum不是经常改变的,所以不必要因为经常改变属性的改变而改变。那么具体怎么来设计呢?我们可以这样做,在ForumState对象中设置一个状态位,表示它的状态是否已经改变,当Forum状态发生改变,比如有人创建新的帖子或者回复了帖子后,我们可以设置这个状态位为true,表示状态已经改变,这样当再次从 缓存中取得Forum时,查看状态位,如果发现已经变化了,那么就重新从数据库加载ForumState。当然要想达到这种效果,我们一定要设计好聚合,所有对子对象的访问都要通过聚合的根,比如所有对ForumState对象的访问都要经过Forum对象,并且要保证所有的数据库操作,都首先从统一的缓存入口进行,这样保证了整个系统中用的是同一个 缓存,大家操作的所有对象都是同一个 缓存中的对象。所以这里也给出了一条对象设计的提示,将经常变化的熟悉和不经常变化的属性分开,并且将经常变化的属性独立出去,作为聚合根的 一个子对象,这样做到变和不变分离,不仅有利于高内聚,而且有利于事务的控制和 缓存的更新。

下面我说一下关于Jdon缓存的一些想法,在jdon的Forum和ForumState对象的 缓存设计中,我看到了Forum有个embedded位表示对象中嵌套的对象是否已经加载完整,当得到Forum对象后,如果发现ForumState已经加载了,那么就直接返回,当有论坛有回帖或者是创建帖子的时候,就重新加载ForumState.还有一种方法也可以这么实现。可以在ForumState对象中增加状态是否改变的标志,那么当我们创建一个主题或者回复帖子的时候,首先从系统 缓存的统一入口处拿到Forum,然后通过Forum得到ForumState对象,并设置ForumState状态改变标记为true,这样以来,当下次从 缓存取出Forum对象的时候,检查两个标记:embedded和 stateChanged,如果embeded为true,但是stateChange也为ture,那么我们重新加载ForumState对象,然后设置stateChanged为false.当然了这样需要对stateChanged的操作同步。这样设计的好处就是将对模型对象的操作都封装在模型对象内部,便于多线程环境下的并发访问控制。因为从把所有对对象的操作都封装起来,方便多线程下的并发同步问题。

上面是关于Repository在对象建模中的作用,下面我也说说关于工厂的作用,既然是工厂,那么它肯定要生产东西出来,但是它不能随便乱生产,它生产出来的应该是整个聚合的根,并且要保证这个聚合的不变量受到保护,这样通过工厂提供一个集中的模型创建的访问点,也方便了控制访问。要设计一个好的工厂,我们首先需要设计一个好的对象模型,分辨出合理的聚合,这样工厂才会发挥真正的效力。

最后既然说到了 缓存,还有一点需要注意,那就是这个 缓存的范围,我这里说的 缓存是全局的 缓存,是一个application级别的 缓存。我个人是比较反对在Httpsession中保存大量数据的,这样当用户增多的情况下,比较会浪费很多的内存,浪费性能。所以我们更应该需要的是全局的 缓存,这样底层的缓冲框架我们还可以采用分布式的 缓存系统,这样以来我们的系统在集群环境下也免去了一些session failover的开销。这其实也是一种SNA架构的思想。

[该贴被xmuzyu于2009-04-25 10:34修改过]
[该贴被admin于2009-04-26 20:03修改过]

                   

11
banq
2009-04-26 20:17

写得很好,你已经明白了Evans DDD魅力重点:聚合根和不变性,这个不变性反应了领域需求的内聚性,而万事万物之所以成为那个事物,必然有其内聚性,算法无论再复杂强大,它也是一个有内聚性的事物(这就是哲学观点)。

所以,抓住聚合根和不变性,就是抓住需求的本质。这是Evans DDD巨大贡献,它指明了什么是需求本质,这是一个通用的原则,真的会让所有软件编制都很简单。

软件在具体实现时又和缓存有关,因为对象是内存中对象,这是我们默认的语义前提,而对象不但可以在一台机器内存中运行,而且通过网络在多台机器之间传输并运行,这种无边界的灵活思路无疑为我们为各种问题提供很大平台基础,没有边界约束,打破了那些所谓数据库概念定义的边界。

需求本质 ---> 聚合根 不变性 ----> 缓存和内存计算 ---->云计算,这是一个体系,一个新世界。

抓住聚合根,就可以很容易处理内存中对象,这些都是一通百通,一顺百顺的一个体系,没有遨游其中的人是很难体会到编程的乐趣。

编程从此是思维逻辑的自然延伸,所有人都会死,苏格拉底是人,所以,苏格拉底也会死,当我们的DDD水平上升到一定程度,我们可以程序语言很自然表达这个逻辑,简洁易懂,就象另外一种人类语言一样,抑或是外星人语言。

所以,大部分现在的人是无法懂外星人语言,这是情有可原,但是我们不能掩盖这样事实。

Declarative programming声明性编程
http://www.jdon.com/jivejdon/thread/36035.html

[该贴被admin于2009-04-26 20:39修改过]

xmuzyu
2009-04-26 21:41

>>需求本质 ---> 聚合根 不变性 ----> 缓存和内存计算 ---->云计算

非常精辟的一句话。我觉得:业务对象建模+业务对象缓存==优良的系统性能解决方案。

至于业务对象建模来说,DDD中的每一种模式都不能少,比如仓库,工厂,聚合,首先聚合使得对象之间的关系更加清晰,同时也使得对象的封装更加的严实,这样带来的好处就是容易控制其生命周期。仓库屏蔽了数据库,同时也屏蔽了缓存,我们在用对象的时候,从仓库中拿,至于这个对象原来是在缓存还是在数据库我们不管,反正仓库保证给我的对象是一个完整的,满足不变量约束的对象,由此以来仓库管理了对象在创建以后的生命周期,对象完全是由我们系统设计者自己控制,而不是用完了就扔掉,这样多浪费资源,呵呵,因为自己用完了,别人说不定还用得到嘿嘿(假如自己和别人都是不同的线程)。


[该贴被xmuzyu于2009-04-26 21:44修改过]

banq
2009-04-28 12:15

>仓库屏蔽了数据库,同时也屏蔽了缓存,我们在用对象的时候,从仓库中拿,至于这个对象原来是在缓存还是在数据库我们不管,反正仓库保证给我的对象是一个完整的,满足不变量约束的对象

我的理解和你不同,仓储Repository只是屏蔽了数据库,而不是缓存,因为缓存就是domain对象的最本质的生存空间,如果没有缓存内存,那么对象object何以存在呢?(没有内存就没有地址空间,对象就无法加载,这是底层机制)

我们控制聚合根和聚合边界内对象的生命周期,就是控制它们在缓存这个空间中的一致性,DDD中只是谈到了一致性,但是这个隐含的场景是唯一性,整个内存空间只有一个聚合根和气边界,不能存在两个以上,否则每个请求都创建自己的聚合根,请求完就扔了,每次请求来再重新构建聚合根和边界很多对象,这会很慢哦,性能很差,这还是依赖数据库的思维哦。

聚合根和边界的子对象们一直存在内存中,进行业务计算,保存业务计算状态和结果,同时数据库中也有一份这些对象中数据和关系的备份,但只是数据备份,是压扁了的对象数据,就像我们将折叠椅子保存到仓库中,拿出来用时,必须把折叠椅展开才能用;聚合根和边界的子对象必须在内存中展开才能用哦。

具体可以看看JiveJdon3.6的代码,在messageKenerl中,数据库持久只是一个单独的动作,在某个时刻对于修改内容来说,是否持久保存到数据库,根本不影响我们业务,见updateMessage方法,你可以屏蔽了messageTransactionPersitence这个动作,当你修改帖子之后,大家还是能够看到帖子修改后的内容,因为我们修改了内存缓存中这个帖子,但是没有保存。

如果这个时候服务器当机,当然我们的修改结果,下次重启就没有了,那么我们还是需要messageTransactionPersitence持久的,但是我们的业务不再依赖它,它可以放在一个异步机制或抛新的线程来执行,与我们业务无关,只是一个save保存动作,就像我们编辑文本打字,经常按ctrl-s保存一样。



xmuzyu
2009-04-28 12:43

呵呵,可能我的表述有问题,我的意思是仓库控制了对于缓存的访问,系统中的所有的仓库都引用全局的缓存入口,而我们获得对象是从仓库里面获取,这样有一个统一的地方来管理缓存,不然我们系统中的其它地方还要引用全局的缓存入口去获得对象,这样有点散乱呵呵。

>>可以屏蔽了messageTransactionPersitence
呵呵,屏蔽这个可以,但是如果下一个请求访问的时候,缓存中的对象被清除了,那么这个请求就看不到最新的结果了呵呵。


呵呵,如果缓存中的对象在清除的时候能得到某种基于事件的通知,那么当对象被清除的时候,将其持久化,这样我们甚至不需要每次更新的时候都持久对象到数据库,直接操作缓存中的对象,当然这就要求有一个经过严格并发测试的可靠的缓存系统为我们的系统服务呵呵。

21Go 1 2 3 4 ... 21 下一页