freeren
2009-04-29 10:22
哈哈,又见很精典的讨论贴,学习了,真的学习了!

不过小弟在此也有个问题和各位请教一下:

在楼主与banq老师的讨论中除了谈到仓库和工厂、缓存,但没谈到池这东西,想知道池应该是做为工厂呢还是做为仓库或者是其他的?

xmuzyu
2009-04-29 10:24
>>所以,从将读写数据库分成两个数据库的性能优化概念出发,将读的数据库用缓存内存的对象来替代,不但解决性能问题,而且无缝和需求分析,领域建模对接,多么美妙的事情。

同意。并且缓存还有一个优点就是:它可以更具系统运行的情况动态的来适应系统的要求,缓存主动迎合系统的要求。比如:缓存的置换算法一般情况下都采用LRU,这样以来,缓存中的对象其实是经常被用到得,而那些不经常使用的,缓存系统已经控制将其移除了,所以从这个角度来说,缓存就好像系统的母亲一样,能根据系统这个孩子的运行时状态,动态的满足孩子的要求。

不过根据我在缓存实践来说,我发现要想实现缓存,面向对象的设计是一个挑战,如果对象的状态没有封装好,这样系统中到处都充斥着改变对象状态的代码,根本没有办法控制缓存中对象状态的并发访问控制。所以要想用好缓存,我们首先要设计好对象模型,能用private的地方尽量用private,这样的目的是封装。能用final的地方尽量用final,因为final类型的变量,JVM可以保证初始化安全性,这样更加容易做到安全的发布。

ps:最近越来越发现<<java 并发编程实践>>经典了。

xmuzyu
2009-04-29 10:32
>>在楼主与banq老师的讨论中除了谈到仓库和工厂、缓存,但没谈到池这东西,想知道池应该是做为工厂呢还是做为仓库或者是其他的?

呵呵,个人观点是:池不需要我们手的的实现,因为容器帮我实现了,这也是Java发展到今天的格局。比如目前的轻量级的IOC容器或者EJB容器其实底层都有池的概念,它的目的就是通过对象的复用来提升性能。并且池里面的对象有个特点:那就是没有标识,它不区分谁是谁,采用池的好处就是可以限制并发的线程数量,从而避免因为一时线程数的突然增加导致系统崩掉。

上面是有关的池的,至于池,它里面主要保存的是无状态的功能性的,不需要考虑线程安全问题的组件,而我们的业务模型,通过什么来提升性能呢?那就要靠缓存了。

所以池和缓存都是提升系统性能的手段,只不过池的工作有容器帮我做了,而我们只要集中精力做缓存就好了。

ps:更多关于池和缓存的讨论,请参考<<面向模式的软件体系结构>>卷3,资源管理。如果大家对服务器底层设计感兴趣,也可以看看卷2.

banq
2009-04-29 10:42
>池应该是做为工厂呢还是做为仓库或者是其他的

Pool是处理无状态的,我们现在讨论的是需求分析设计,聚合根实体代表一个现实时空的事物,都是有数据状态在其中。

讨论到这里,我们要明确什么是缓存?缓存=对象唯一性+内存。

缓存就是保持聚合根和边界内对象的唯一性,因为内存有限,而且也没有必要为每个类在不同的客户端事件下,创建一个对象。

这里要谈到经典架构SSH: Struts+Spring+Hibernate中有一个OSIV也就是Open Session In View模式,Hibernate缺省的缓存是Session内的,也就是每次请求处理中缓存唯一的,而不是application级别,全局唯一的,这其实就是Hibernate的一级缓存,是内置,要达到我们这个主题讨论的缓存目标,必须使用Hibernate的二级缓存。

换句话说:也就是使用SSH,就会发生我前面讲的,每次请求都重建一次聚合根和其边界内子对象,这个很耗费性能,也就是根本无法实现聚合根和缓存的唯一性,我们这里讲的美妙设计无法在缺省的SSH架构中实现。所以,很多人就是使用了SSH,还是走到了数据库为中心的编程模型,因为SSH没有缺省提供一个application级别的缓存,这样,如果要实现我前面讲的JiveJdon中updateMessage中功能,就必须自己有意识去做,没人点拨指导是想不到这条路的,这也是我们以前指责SSH容易导致数据库编程问题关键所在。(说句笑话:大家应该明白为什么Jdon框架推出虽然落后于Spring,但是早就达到5.0中级稳定版,而Spring正在向3.0买进呢)

所以,在SSH架构中必须使用二级缓存,手工显式配置缓存,然后,做好自己的工厂,保证缓存中聚合边界内的对象们都是引用指向的是这个边界内的对象,不能指错了,如果存在两个聚合体对象群,其实它们是反映需求中一类聚合设计,那么变成多个对象群,边界内对象互相引用,陷入失败地步。

有了以上前提保证唯一性以后,java 并发编程实践就能派上用场了,我们甚至可以利用Java并发锁在内存中来优雅高性能解决并发写问题,这样避免数据库锁的低性能和粗粒度的排他性。

相关并发锁帖子:

http://www.jdon.com/article/34773.html

[该贴被banq于2009-04-29 10:43修改过]

freeren
2009-04-29 11:31
呵呵,看了banq老师与楼主的回答,明白了很多,谢谢!

>>至于池,它里面主要保存的是无状态的功能性的,不需要考虑线程安全问题的组件,而我们的业务模型,通过什么来提升性能呢?那就要靠缓存了。

茅塞顿开啊!

至于老师提的SSH中的hibernate的缓存(不管一级--session级还是二级--全局),我们公司目前都不用,本人一直推荐,可惜公司怕所谓的风险(本人觉得风险在于一开始的需求分析设计,而不是在于有没有用过),所以公司目前所谓的缓存基本是用web 容器的application--在容器启动,把一些基础数据加载进来的形式,还有就是死命往httpsession中加数据的形式!看来这样的风险更大!

yuer0
2009-05-13 13:40
受教了,非常感谢各位!

分析领域核心模型,使之凌驾于缓存之上,把曾经的数据库操作就交给后台的Runner。

非常激动人心的世界,值得思索。

banq
2009-05-13 18:04
感谢yuer0肯定,与yuer0英雄所见略同,总算有同行者。

[该贴被banq于2009-05-13 18:05修改过]

xmuzyu
2009-05-13 18:31
呵呵,终于有个志同道合的人了,不容易啊。

yellowcat
2009-05-16 14:39
引用banq大哥的话:

“缓存=对象唯一性+内存”

(这句话真是经典的话!!!)

我们这里讲的美妙设计无法在缺省的SSH架构中实现

(其实这种大哥说的这种美妙的设计在GAvin_king在他的著作中早有以下评论:

“对于典型的web或者企业应用程序,持久化上下文范围(这里也就是session的范围)的同一性是首选(也就是session per request),跨多个工作单元(这里的工作单元其实也可以理解为request)的实例重用,在高速缓存利用和编程模型方面提供了一些潜在的优势,但是在一个遍布多线程的应用程序中,始终在全局的同一性映射中(也就是session per application)同步对持久化对象的共享访问成本太高,难以支付。更简单并且更可伸缩些的办法是,让每个线程在每个持久化上下文中使用一组独特的持久化实例”

)

gavin_king引入了一个概念就是利用hibernate提供的这一层持久化层来尽量减少数据库命中率(说白了就是提供缓存),并可通过2个方法来实现:

1.利用托管对象(说白了就是一个持久化对象在request结束则session关掉变为托管,这个托管的对象形成一层缓存,第二次requst来就不用命中数据库load了,直接reattach,又变成持久对象)(session per request with detached object)(同时要解决好同一性问题也就是banq大哥所讲的唯一性),因为提供缓存必须要保证聚合根的唯一性

2.扩展持久化上下文的扩展(说白了就是在两个request之间session不关)(session per conversation)这里就不用考虑唯一性问题,因为在一个session中(这个很重要)只要数据库记录相同,hibernate自动会保证java对象的同一性。

xmuzyu
2009-05-16 23:34
缓存用的地方非常多,比如处理器缓存(这就导致了并发编程的内存可见性),JVM缓存,缓存范围也很多,request,session,application或者cluster,根据缓存内容也可以划分好多种,比如静态内容的缓存,model缓存。yellowcat兄谈到的是session级别的缓存,不过它也是一种model的缓存。

而这个帖子的主题意思其实就是全局的(application,cluster)缓存,这是一种model缓存,针对业务对象的缓存。这种缓存的意义不仅在于减少了数据库的访问,也很大程度的减少了数据库事务给性能造成的影响,因为很多的操作,我们都可以通过内存锁实现,而不是依靠数据库事务的隔离级别实现并发控制,这个时候的并发有应用程序来控制,有点像悲观离线锁模式,而事务只需要保证数据完整性和持久性。

yellowcat
2009-05-17 12:39
离线并发谈何容易,一个粗粒度锁或者一个悲观离线锁设计需要很多领域知识,其实已经上升为需求分析级别的设计了

xmuzyu
2009-05-17 13:06
>>离线并发谈何容易,一个粗粒度锁或者一个悲观离线锁设计需要很多领域知识,其实已经上升为需求分析级别的设计了

确实是这样。尤其是集群环境下实现内存悲观离线锁更加困难。我以前实现过一个是通过数据库表来实现悲观离线锁。对于内存悲观离线锁,实现难度比较大。所以目前我实现的内存锁都是针对某一个model,而不是所有model共享锁。

[该贴被xmuzyu于2009-05-17 13:07修改过]

yellowcat
2009-05-17 13:20
以下引用martin fowler的观点:

并发问题由来已久,人们提出了各种不同的解决方案,对于企业应用来说,有2个非常重要的解决方案:隔离isolation和不变性immutability.

并发问题发生在多个执行单元(例如线程)同事访问同一片数据的时候,一个解决办法就是隔离:划分数据,使得每一篇数据都只能被一个执行单元访问,操作系统为每个进程单独分配一片内存,并且只有这个进程可以对这篇内存进行读或写。同样的还有文件锁。好的并发设计应该是:找到各种创建隔离区的办法,并且保证在每个隔离区里完成尽可能多的任务。

只有在共享数据可以修改的情况下,并发问题才会出现,所以一个避免冲突的方法是识别那些是不变的数据,把它们从程序中分类出来,然他们只使用拷贝的数据源。

在应用上面2个策略以后,还剩下一些可变数据无法隔离时候,对应这些部分才应用乐观,悲观和粗粒度并发控制策略

[该贴被yellowcat于2009-05-17 14:13修改过]

yellowcat
2009-05-17 13:55
仅仅将一系列系统事务依次连接在一起是不足以支持一个业务事务的,应用程序必须采取措施将他们粘合在一起。

业务事务原子性和持久性是最容易的,因为业务事务启动一个系统事务,系统事务保证了修改的数据将作为一个单元而提交,并将被持久化。所以这样就能保证业务事务原子性和持久性了。

业务事务最麻烦的是隔离性,没有隔离性就没有一致性

[该贴被yellowcat于2009-05-17 13:55修改过]

[该贴被yellowcat于2009-05-17 14:04修改过]

yellowcat
2009-05-17 14:20
》》所以目前我实现的内存锁都是针对某一个model,而不是所有model共享锁。

粗粒度锁模式的概念就是用一个锁锁住一组相关的对象

ddd当中聚集的特性(所谓数据修改的基本单位)需要使用粗粒度锁,因为对其中任何一个成员的访问都要对整体枷锁,

猜你喜欢
7Go 上一页 1 2 3 4 ... 7 下一页