缓存仅仅只能保证放入和读取操作线程安全
-----缓存凭什么保证线程安全?
对象的缓存中,首先是对象的设计,只有设计好了对象才适合放到缓存中
-----没有对象就不适合有缓存么?
这样通过细粒度的锁来提高系统的并发量。
-----锁和并发量有什么关系?
而业务对象的线程安全性要根据具体的项目需求
-----什么样的线程是安全的?
对象的缓存中,首先是对象的设计,只有设计好了对象才适合放到缓存中
-----没有对象就不适合有缓存么?
这样通过细粒度的锁来提高系统的并发量。
-----锁和并发量有什么关系?
而业务对象的线程安全性要根据具体的项目需求
-----什么样的线程是安全的?
这个问题前面已经有人说了。我这个帖子的主题就是首先面向对象设计,设计好对象,然后放入缓存,设计好对象了,你不用缓存也可以,只不过用了缓存性能会好。请看了前面的帖子再发帖,这个问题前面已经讨论过了。
这样通过细粒度的锁来提高系统的并发量。
-----锁和并发量有什么关系?
锁的粒度越粗,并发线程就要等待,这样一个线程持有锁的时间越长,其它线程等待就越长,能真正并发进行的线程数就越少。
而业务对象的线程安全性要根据具体的项目需求
-----什么样的线程是安全的?
这个问题具体问题具体分析。
PS:我只所以说业务对象要线程安全是因为有些对象是要全局并发写操作的。这些是我自己的做法,如果你觉得不好或者不对,你完全可以依你自己的做法去做。
对象的缓存中,首先是对象的设计,只有设计好了对象才适合放到缓存中
-----没有对象就不适合有缓存么?
这个问题前面已经有人说了。我这个帖子的主题就是首先面向对象设计,设计好对象,然后放入缓存,设计好对象了,你不用缓存也可以,只不过用了缓存性能会好。请看了前面的帖子再发帖,这个问题前面已经讨论过了。
<<<其实缓存和对象之间是没有任何关系的。
这样通过细粒度的锁来提高系统的并发量。
-----锁和并发量有什么关系?
锁的粒度越粗,并发线程就要等待,这样一个线程持有锁的时间越长,其它线程等待就越长,能真正并发进行的线程数就越少。
<<<这个说的还对。锁的粒度越粗就是指锁的范围越大。拿一个对象做例子:一个user对象里面有name、age,如果2个一起锁,那么一个进程操作name的时候其他进程就不能操作age;如果只锁name,一个进程操作name的时候其他进程可以操作age。
而业务对象的线程安全性要根据具体的项目需求
-----什么样的线程是安全的?
<<<线程安全就是一个线程运行的时候不能影响另一个线程,也就是资源不会造成冲突。公共资源包括共享内存、文件。所以线程要安全就必须有锁。
是的,我也没有说过缓存和对象有关系,为什么大家都问了同样的问题。我前面也说过了,缓存存在的地方非常多,我这里只是借用缓存来缓存对象。
还有就是那个线程安全方面的问题。也许系统中只有一部分对象需要考虑到线程安全性,而其他一部分只要不涉及全局写操作,都可以通过复制来解决线程安全问题,这样也就不需要通过锁来保护了。
其实线程安全也不一定都要涉及到共享,共享的时候线程安全就可以通过锁来进行,但是有时候为了线程安全,我们采取的是不共享,也就是线程限制也可以说是任务限制(比如复制对象副本给每个线程,还有比如ThreadLocal等,这些都是通过不共享对象来实现线程安全的)。这是我的理解。
还有个问题就是谈了很久缓存,到底缓存在什么地方。抛开独立的缓存服务器。对于缓存和应用在同一台机器上的情况,缓存和应用是存在于不同进程还是一个进程,或者说是否在一个JVM里。
缓存里的对象是否一定要取出,才可以被访问,然后还要放入,才可以被其他线程取出访问。这样的话,业务对象上的细粒度锁就没有意义了,因为缓存为保证放入和读取的线程安全,必须把整个对象上锁。更糟糕的情况可能要把整个对象树上锁,防止两个线程各自等待对方释放对象树里其他对象造成的死锁。
如果可以直接访问缓存的业务对象,那么缓存的对象又和存在于应用本身JVM内存里的对象有什么区别。都是直接访问,都是业务对象自己保证数据一致。
由于内存有限,缓存和数据库一样,都不可以把数据完全放入内存。因此,缓存可以节约的时间应该是由数据生成对象的时间,I/O时间几乎无法节约,因为可以把缓存占用的内存给数据库作cache,同样可以避免物理读写。这种节省的前提还是业务对象被频繁访问,可以始终存在于缓存。否则,每次访问反而增加了缓存管理的开销,性能更差。
OO可以用比较接近人思维的方式设计系统,逻辑更清晰,但是未必适合机器处理,性能并不一定好。
考虑一个简单例子,订单变化会影响客户的信用额度,订单和客户额度应该是两个对象,并非订单的每个变化都会影响额度,因此额度对象未必在缓存里,OO的情况下,为了更新额度,必须产生额度对象,涉及数据库访问,缓存管理等等。更新对象后,还要同步缓存。而传统方式,只是一条update语句,或者再加上一个行级锁。数据库读取,对象生成,缓存管理,全是无意义开销。
所以,OO加缓存未必在性能上优于传统方式。采用哪种方式,要看具体情况,谁也否定不了谁。
恩这个我同意,但是在没有用缓存的时候,很多问题不需要考虑,而在用到缓存的时候,就需要考虑,我那个不变量指的是如何在多线程环境下保证不变量,在不采用缓存的时候,每个业务对象都是在一个任务边界中执行的,而这个任务边界一般就是独立的线程,这个时候不存在多线程共享业务对象,所以不会考虑多线程并发访问下如何保证不变量。
>>这样的话,业务对象上的细粒度锁就没有意义了,因为缓存为保证放入和读取的线程安全,必须把整个对象上锁。更糟糕的情况可能要把整个对象树上锁,防止两个线程各自等待对方释放对象树里其他对象造成的死锁。
这个不太同意。首先将对象放入和取出的时候,缓存系统是不需要对这个对象加锁的。缓存系统容许多线程并发读取的。你所说的把整个对象加锁就类似于当个数据库事务中的悲观锁,在真正的缓存系统中不会采取这种方式。
>>因此,缓存可以节约的时间应该是由数据生成对象的时间,I/O时间几乎无法节约
这个不太同意。首先缓存一定大大减少了IO时间,可以推荐一本书给你看看,数据访问模式,这本书就是专门讲解如何在面向对象的系统中解决数据访问给性能带来的开销的。这本书整本书都是将如何减低数据IO操作来提升性能的,而有很大的篇幅就是讲缓存的。
>>OO可以用比较接近人思维的方式设计系统,逻辑更清晰,但是未必适合机器处理,性能并不一定好。
这个同意。性能不好,所以我才说要缓存业务对象。
>>所以,OO加缓存未必在性能上优于传统方式。采用哪种方式,要看具体情况,谁也否定不了谁。
OO加缓存不仅仅是性能方面的原因,这是片面的,OO还有一个非常大的好处,可扩展和可维护性方面的增加。
我同意对象缓存可以减少物理I/O,但是其他缓存,例如数据库cache也可以完成同样工作。如果数据本来就在cache里,根本就没有物理I/O,对象缓存又如何减少根本就不存在的操作呢?
增加一级对象缓存要消耗内存,如果同样数量的内存用于数据库cache是否会更多的减少I/O呢?毕竟对象要比组成它的数据占用更多内存。
OO可以提高维护和扩展性,但是由于性能上的缺陷,我认为OO未必是解决大并发高负载应用的最好方案。
这个与缓存关系非常大,首先缓存存在一个命中率的问题,并且缓存的填充方式也有好几种,比如即时填充(适合不能预知何时被访问的对象,而预先填充适合能预知的对象),缓存刚开始也需没数据,随着系统的运行,经常用到的对象会被放到缓存中,(因为缓存可以设置很多过期策略的),而那些不经常使用的对象是不会放到缓存的。
>>我同意对象缓存可以减少物理I/O,但是其他缓存,例如数据库cache也可以完成同样工作。如果数据本来就在cache里,根本就没有物理I/O,对象缓存又如何减少根本就不存在的操作呢?
近则快的道理也许你应该明白。里客户端越近效率最好,这就是我一般都不回用hibernate2级缓存的原因,我自己的项目中,一般都是通过AOP和装饰器透明的增加业务层的缓存。并且业务对象缓存是针对具体的业务的,一个业务对象也许由数据库中的好几张表中的数据填充的,比如DDD中的聚合就有可能是有好几张表中的数据填充的。而数据库的cache太底层,根本没有针对具体的业务需求进行缓存。
>>OO可以提高维护和扩展性,但是由于性能上的缺陷,我认为OO未必是解决大并发高负载应用的最好方案。
性能差不是因为OO,是因为没有用好OO。
哎,本来想讨论点如何通过面向对象的设计设计出业务对象模型,然后放入缓存的,结果最后都变成讨论缓存了。
缓存的重要作用分两层是:
第一,首先断绝了面向数据库的编程思路,你要面向缓存中的对象编程,这个话题xmuzyu也谈过了,其实也是内存计算模式。
第二,缓存是一把刀,切断了传统的编程思路,使的可切分Partition 以及Asynchrony 异步实现成为可能,从而为程序引入伸缩性打开了一个入口。
Partition everything和Asynchrony everything在本周JavaOne大会上被eBay架构师就谈到了,他们总结一下他们建立大型系统的经验,最佳实践就是:
1. Partition everything.(切分任何事情 包括业务领域知识)
没有切分就没有伸缩性,把大问题切分成小问题,这其实就是OO细分松耦合的概念,在ebay没有数据库和应用服务器的称谓(因为数据库和应用服务器都是盒子的概念,不可切分的),有的只是可切分的领域功能,这些领域是跨层或者可负载平衡的。
2. Asynchrony everywhere.(在程序中任何可能地方引入异步,就象前面谈到在JiveJdon中引入缓存后,可以异步引入数据库储存)。
通常我们的程序都是同步系统,我们程序员也都是同步串行化编程思维,这也是并行程序员很少的原因,同步就是一个请求马上又反映,我发出请求你必须立即给我响应,这其实有“刚”的成分在里面,当系统访问量大增,刚则易断了,系统当机或停止服务的原因是因为访问量大增,这是很不专业和外行的奇怪逻辑。本质是对架构的愚昧和无知。
ebay倡导尽可能到处使用异步,追求异步最大化,这其实就是业务和oo设计或架构设计的博弈,作为架构师,你是战斗一方,你不是局外者,你必须秉承将异步胜利进行到底的理念和业务做斗争,将业务全部锤散直至不能再散为止,这个没有尺度数量标准的,因为不同业务不一样,也取决于你的能力。
ebay是从事件这个架构高度出发,将软件看成一个事件响应器,软件死机崩溃就是无法对后续事件处理,那么我们把Event Queue事件专门做成一个Queue队列,进行排队处理,做到事件不丢失,因为你一旦丢失一个事情或者无法处理,就是用户的抱怨和客户流失。当然低层性能人士会跳出来说性能问题,如果我们有一个并行强大的事件处理设计,那么就不会有性能问题,就象你食堂里,为什么排队买菜,不是你排队这个选择选错了,是卖菜的人忙不过来,要看问题本质。
ebay使用消息机制来广播修改通知,这也是典型的一个缓存更新的大架构。
3. Automate everything:将更多事情自动化,机器处理,比如用户个性化的东西可能不能用缓存,但是我们可以研究这个用户的习惯操作和思维;使用网管软件之类工具自动探测机器的健康状态,及时报告。
4. Remember, everything fails.(单点风险,也就是提高可靠性,使用冗余策略)
5. Embrace inconsistency.(积极面对不一致性)
一致性也可以从缓存更新这个角度理解,当缓存中共享对象状态更新后,是不是所有机器上这个对象状态都必须立即同时更新?没有这个必要,可以根据业务需求来决定,有些需求就没有必要立即更新,比如user preference等。
为了能够提供立即更新的一致性, eBay避免使用一些可分布的事务比如客户端端的JDBC, 提高一致性最大化的方式就是使用状态模式,状态机,也就是内存或缓存中对象状态机,以及小心处理数据库操作的次序;事件一致性可以通过异步事件或轻量可重复执行的柔性的批处理来完成。
http://java.sun.com/javaone/2009/articles/gen_ebay.jsp
[该贴被banq于2009-06-06 16:54修改过]