受教了,非常感谢各位!
分析领域核心模型,使之凌驾于缓存之上,把曾经的数据库操作就交给后台的Runner。

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

感谢yuer0肯定,与yuer0英雄所见略同,总算有同行者。

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

呵呵,终于有个志同道合的人了,不容易啊。

引用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对象的同一性。

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

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

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

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

确实是这样。尤其是集群环境下实现内存悲观离线锁更加困难。我以前实现过一个是通过数据库表来实现悲观离线锁。对于内存悲观离线锁,实现难度比较大。所以目前我实现的内存锁都是针对某一个model,而不是所有model共享锁。
[该贴被xmuzyu于2009-05-17 13:07修改过]

以下引用martin fowler的观点:
并发问题由来已久,人们提出了各种不同的解决方案,对于企业应用来说,有2个非常重要的解决方案:隔离isolation和不变性immutability.
并发问题发生在多个执行单元(例如线程)同事访问同一片数据的时候,一个解决办法就是隔离:划分数据,使得每一篇数据都只能被一个执行单元访问,操作系统为每个进程单独分配一片内存,并且只有这个进程可以对这篇内存进行读或写。同样的还有文件锁。好的并发设计应该是:找到各种创建隔离区的办法,并且保证在每个隔离区里完成尽可能多的任务。
只有在共享数据可以修改的情况下,并发问题才会出现,所以一个避免冲突的方法是识别那些是不变的数据,把它们从程序中分类出来,然他们只使用拷贝的数据源。
在应用上面2个策略以后,还剩下一些可变数据无法隔离时候,对应这些部分才应用乐观,悲观和粗粒度并发控制策略
[该贴被yellowcat于2009-05-17 14:13修改过]

仅仅将一系列系统事务依次连接在一起是不足以支持一个业务事务的,应用程序必须采取措施将他们粘合在一起。
业务事务原子性和持久性是最容易的,因为业务事务启动一个系统事务,系统事务保证了修改的数据将作为一个单元而提交,并将被持久化。所以这样就能保证业务事务原子性和持久性了。
业务事务最麻烦的是隔离性,没有隔离性就没有一致性

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

》》所以目前我实现的内存锁都是针对某一个model,而不是所有model共享锁。
粗粒度锁模式的概念就是用一个锁锁住一组相关的对象
ddd当中聚集的特性(所谓数据修改的基本单位)需要使用粗粒度锁,因为对其中任何一个成员的访问都要对整体枷锁,

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

这个不太认同。DDD中的聚合讲究将可变与不变分开,聚合根控制对其聚合子对象的访问,加锁不能加在整体聚合根上,相反要加在子对象,这样减低对锁竞争。

另外请参见我的另外一个帖子:

关于jivedon的并发问题,请板桥老师指点。

http://www.jdon.com/jivejdon/thread/36231.html

》》所以目前我实现的内存锁都是针对某一个model,而不是所有model共享锁。
粗粒度锁模式的概念就是用一个锁锁住一组相关的对象
ddd当中聚集的特性(所谓数据修改的基本单位)需要使用粗粒度锁,因为对其中任何一个成员的访问都要对整体枷锁,要打到这种状态,就要进入根对象锁,锁住根对象就锁住了聚集中的所有对象,根锁提供了单一控制点。
要使用根锁作为粗粒度锁,据必须为聚集对象提供到根对象的导航方法。这允许在要对聚集中某个对象枷锁时,枷锁机制能找到它对应的根对象并锁住根对象。
应用粗粒度锁主要是达到业务的目的,比如三个对象,一个拍卖货品和他的价格和描述,拍卖货品是描述和价格的聚合根,修改价格的时候要根据价格对象中的货品引用找到聚合根货品并锁定他,然后如果另一线程视图修改描述的话,那么会因为聚合根货品被锁定而不能进行修改,因为聚合根被锁定了,
比如一个货品,描述是金戒指,价格是5000元,2个线程如果可以同时分别修改描述和价格,在业务上面就说不过去了,所以要锁就得锁住整个聚合。
[该贴被yellowcat于2009-05-17 14:55修改过]

马丁大叔的粗粒度锁是实现离线悲观锁的机制,而我们这里的model是放在全局的缓存中,是有系统所有的线程来访问的,只能采用锁分离技术来进行,否则对缓存对象的竞争可能已经大大折扣了缓存对性能的提高。悲观离线锁里面主要是为了隔离,而我们缓存中的model主要是为了全局共享,全局共享采用粗粒度锁只能减低性能。

>比如:个拍卖货品和他的价格和描述,拍卖货品是描述和价格的聚合根,修改价格的时候要根据价格对象中的货品引用找到聚合根货品并锁定他,然后如果另一线程视图修改描述的话,那么会因为聚合根货品被锁定而不能进行修改,因为聚合根被锁定了。

这是对的,但是需要补充的是,修改价格的时候要根据价格对象中的货品引用找到聚合根货品并锁定他,这个锁定我们可以使用具体业务方法锁,而不是把整个货品对象锁定,更不能将聚合对象群所有对象全部锁定,我们在某个业务方法中加锁,实现串行化,保证一致性和原子性,这样根据具体操作枷锁策略比较灵活。

具体可见JiveJdon中ForumThread的addNewMessage方法,这个方法枷锁了,但是没有将ForumThread整个对象锁定,而用数据表来实现,可能就要锁定整个数据表,这也是我们认为内存锁策略+领域模型的新的优势,这也是我们一直引以为豪的,DDD书中讲的也是基于数据表的悲观锁问题。

难道我们不小心又超越了大师,还是我们走在一个错误的自我感觉中,不过从JiveJdon运行中目前没有发现这个方案的问题,也欢迎理论探讨。


[该贴被banq于2009-05-17 15:45修改过]