`
OneEyeWolf
  • 浏览: 104547 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

应用层缓存 VS ORM缓存

    博客分类:
  • tech
阅读更多

   最近做一个比较大的电子商务项目,预计每天订单量将在5万多单,客服人员需要频繁的下单、查询订单、操作订单,客人预订完订单后,会立即进入处理流程,为了提高服务质量,要求流水化作业,平均要在40分钟-80分钟内处理完订单,对于疑难订单要到第二天,才能处理完。所以订单在创建后,会在短时间内,被频繁的修改和查看。
  由于在项目中ORM层主要是基于Hibernate框架,所以在调优时,很自然的就想到打开Hibernate的二级缓存,以次来减小由于Load 订单大对象时N+1次查询给数据造成的压力,自己做的测试效果也非常好,也顺利通过压力测试。
  但在上线时,性能却并不佳,经过分析业务的操作特点,查找原因有以下几占:,

    1.但由于中台每天在工作当中,频繁的批量分配工单,
   
    因为要批量将订单分配给某一个工作人员处理,在代码当中执行了一个bulkUpdate的操作:
  template.bulkUpdate("update order set owner = ? where id in (?, ?,?)");
   
    这时Hibernate会直接将Order对象的二级缓存清楚掉。
    由于二级缓存,总是被刷掉,再查询时,需要重新从数据库Load,所以二级缓存变相直接起的作用很少。

    2.由于工作人员在处理订单时,每一次查看之后,都有更新操作,在更新之后,订单被清除缓存,下一组人在处理订单时,又得重新LOAD,所以效果并不好。
    3.无论是白盒测试,还是压力测试时,所基于的案例太过于简单,没有更深入的模仿业务操作,对于压力测试的脚本,也很难精确的模拟出真实的流程化的业务操作过程。


    开始想到,直接获得Session,直接使用JDBC来编写更新代码,并在更新后,使用sessionFactory.evict(Order.class, id);来有目标的逐个清除特定的对象,以避免全部清楚缓存。
    但样做,会对DAO层,修改过大。

    由于整个模块最核心的商业对象就是订单,最后决定在Service层对订单开小灶,对订单缓存的单独的定制处理。

    我觉得应用缓存存在以下优点:
      1。速度要快于ORM缓存,
      2。对于缓存的控制权更大,可以直接控制缓存工具的API进行操作,可以避免一些盲目清除的操作。
      3。更灵活的控制缓存中对象的失效,如根据事件来清除缓存,如订单的处理流程结束时,将该订单从缓存中清除掉,
      4。在更新数据库时,不是直接清除缓存,而是更新缓存(尽管这有风险),当业务层抛出异常时,才去清空缓存,避免由于频繁更新,而清空缓存。

    缺点:
     1。订单的更新操作,必须是单点的,只能通过IOrderService提供的接口,进行更新操作,否则数据不一致的风险较大。
     2。想要透明化,需要有一定的代码工作量,不容易达到ORM缓存最强大的那种透明化和灵活可配置,你可以使用Ehcache, 也可以选Jboss,有钱的话,可以用Tangosol。

     3。如果不对第三方缓存包,进行一定的封装的话,会直接耦合于第三方的缓存包,不能像Hibernate那样,灵活选择和配置缓存工具。
     4。对业务层代码有一定的侵入

   

    目前的方案是采用应用层的现代化,同时使用如Proxy模式来提供透明化的设计,

      IOrderService -》  OrderService -》 CacheableOrderService

    通过Spring的Bean配置,一样可以实现透明化的操作。


    结论:
    1。缓存的清空与更新,要尽量精确的去操作受到更新影响的对象,而不是全部搞掉。
      在Hibernate当中,也提供了sessionFactory.evict(class, id)这样细粒度的清空缓存对象的方法。
          sessionFactory.evice(class)的操作,要看这样的操作是否频繁,如果频繁,对于缓存的作用就会大大的折扣。
   2。如果缓存对象过多,对于失效的算法与处理,要与业务对象的特性紧密的联合起来,通过事件来驱动对象的失效。
   3。对于商业对象的缓存,必须要深刻分析对象的生命周期,业务特性。
   4。对于数据不一致的风险,要有足够的认识与预防手段。
   5。合理的估计订单对象的大小,分配足够的内存
   6。如果只使用中心缓存,只能减小数据库的压力,对于网络带宽的压力,还是有的,速度上也远远逊于本地缓存的效果,所以要结合本地缓存+中心缓存的策略方案,即提高速度,避免群集复制时的瓶颈。


    

 

 

 

 

  

分享到:
评论
22 楼 jellyfish 2007-06-04  
OneEyeWolf 写道
Lucas Lee 写道
OneEyeWolf 写道
Lucas Lee 写道
我个人感觉这样的缓存似乎不会对整体性能有很大的提升.
缓存对于内存的需求倒不是关键.
需要lz实际做出来后仔细测试一下.


 对于整体性能当然有很大的提升了,压力测试时,单个订单页面的频繁的load,对于数据库的压力很小,如果没有缓存,一个订单有8个子对象,加载订单页面时,相当于几十条SQL,DBA肯定要发通报的。


看起来你做过性能测试了.
能否发一下稍微详细点的性能测试对比数据?


sorry, 测试不是俺做的,load runner的测试脚本,也不在我的机子,所以暂时不能提供。

对于这样的应用层缓存及测试,其实太过于特定的应用,所以大可以不必相信及在其它项目当中采纳。

其实的我关键就是想知道,有没有人做过类似于对商业对象发生更新时,不清缓存,直接更新缓存的做法。

I did something similar - 对商业对象发生更新时,不清缓存,直接更新缓存的做法。The key step is the cache lock. When you read/write, you need to have a consistent locking policy to avoid deadlock/long waiting time. Read the cache lock api doc very carefully, this is the lesson I learned in the hard way. And sometimes, there are bugs in the cache implementation too.

If your app is distributed, you may need to build something like JMS(distributed observer) around the cache api.

Seal the cache logic in a decorator pattern. It makes the implementation clean.

Last, test it extensively, get the timings for different cases, cache vs no cache, concurrent read/write. One case I had was several waiting threads waited too long for the completion of the write.

I think there exists an abstraction between the cache lock api and various apps. If we see more use cases like this, we could abstract it out to a small lib.


21 楼 ahuaxuan 2007-06-04  
一个工作人员一天要处理多少订单呢。
假设8个,那么8条根据id的update是非常快的。
而且在分配订单的时候,订单已经被查到二级缓存,这时候先作查询,在做更新的话,数据库只会接受到8条根据id的update而已。为什么不这样做呢,这样做就可以充分利用二级缓存了啊,如果是用in,集合里有8个id,我看也未必会比8条sql快,in的效率是不高的,即使要这样做也应该是用or啊,俺觉得问题被人为的搞复杂了吧
20 楼 fangang 2007-06-04  
频繁更新的数据就不是使用二级缓存的适用环境,楼主存在的主要问题是使用不当。解决N+1次查询的问题正确方法应当是延迟查询,而不是二级缓存。
19 楼 OneEyeWolf 2007-06-04  
Readonly 写道
你的事务边界是在DAO上?如果在应用层调用多个DAO的话,如何控制事务啊?


这是这个应用比较特殊的地方,订单在更新操作时,就是封装在DAO层内,没有也不允许跨DAO调用。
另外由于没有对SessionInView模式做对压力测试(在生产环境上不能随便做压力测试),得出是否适合这种操作环境,不敢贸然使用,所以延迟加载都是关闭的状态,订单的所有的对象加载,都是在DAO层内完成。
18 楼 Readonly 2007-06-04  
OneEyeWolf 写道

bulkUpdate 是spring封装的方法,真正调用的仍然是Hibernate的

query.executeUpdate(hsql);方法。

如果批量更新,又使用Hibernate, 请问用什么方法,hibernate可以明确的清楚发生更新影响的对象?


因为Hibernate的executeUpdate会通知整个cache region失效,这种操作很频繁地话,会导致cache miss rate非常的高,你前面也说了: 所以二级缓存变相直接起的作用很少。

Hibernate的executeUpdate并不适合用在这种场景下,每天分配给一个人的订单总是很有限的,按照用户选中的order id,一个一个取出Order对象,然后依次更新并不是什么耗时的操作,如果真有性能上的问题,也可以通过设置合适的batch size来解决。而且你这里采用的bulk update语句里面含有in,这会给数据库带来很大的压力。


OneEyeWolf 写道

 应用层缓存,也就是缓存的行为发生应用层,事务是在DAO层完成的,这与事务隔离没有关系,
 更新缓存,一定是在更新数据库成功之后,返回应用层,才进行的操作。

你的事务边界是在DAO上?如果在应用层调用多个DAO的话,如何控制事务啊?
17 楼 Lordaeron 2007-06-03  
OneEyeWolf 写道
zrq 写道
对关键任务的应用程序,不应该使用二级缓存,在集群的情况下,如果使用二级缓存,数据不能保证100%的同步,将会出现不可预测的错误。
象楼主所面对的情况,每天5万的数据量对数据库来说,应该说事小菜一碟。如果性能瓶颈在于数据库,应该在数据库方面考虑,如缓存全表,数据库集群,根据业务需求,优化表的设计。

优化应该从SQL入手,一个订单有8个子对象,一个或两个SQL可以搞定的事,用HIBERNATE 要用几十条SQL,性能当然受影响了。

本人使用HIBERNATE多年,发现它最适合用于单个的save,和 update,对于查询,还是JDBC 最好,当然要经过一些封装。


你可以自己设计一个订单,有8到9个子类,再设计对应的一对多或一对一的库表,然后看看,是否可以用一个或两个SQL搞定,并且封装的很好。

要不要給你的哪段table schema 來, 順便講一下你用的是哪個DBMS
看能不能一兩句搞定.
16 楼 OneEyeWolf 2007-06-03  
Lucas Lee 写道
OneEyeWolf 写道
Lucas Lee 写道
我个人感觉这样的缓存似乎不会对整体性能有很大的提升.
缓存对于内存的需求倒不是关键.
需要lz实际做出来后仔细测试一下.


 对于整体性能当然有很大的提升了,压力测试时,单个订单页面的频繁的load,对于数据库的压力很小,如果没有缓存,一个订单有8个子对象,加载订单页面时,相当于几十条SQL,DBA肯定要发通报的。


看起来你做过性能测试了.
能否发一下稍微详细点的性能测试对比数据?


sorry, 测试不是俺做的,load runner的测试脚本,也不在我的机子,所以暂时不能提供。

对于这样的应用层缓存及测试,其实太过于特定的应用,所以大可以不必相信及在其它项目当中采纳。

其实的我关键就是想知道,有没有人做过类似于对商业对象发生更新时,不清缓存,直接更新缓存的做法。
15 楼 OneEyeWolf 2007-06-03  
zrq 写道
对关键任务的应用程序,不应该使用二级缓存,在集群的情况下,如果使用二级缓存,数据不能保证100%的同步,将会出现不可预测的错误。
象楼主所面对的情况,每天5万的数据量对数据库来说,应该说事小菜一碟。如果性能瓶颈在于数据库,应该在数据库方面考虑,如缓存全表,数据库集群,根据业务需求,优化表的设计。

优化应该从SQL入手,一个订单有8个子对象,一个或两个SQL可以搞定的事,用HIBERNATE 要用几十条SQL,性能当然受影响了。

本人使用HIBERNATE多年,发现它最适合用于单个的save,和 update,对于查询,还是JDBC 最好,当然要经过一些封装。


你可以自己设计一个订单,有8到9个子类,再设计对应的一对多或一对一的库表,然后看看,是否可以用一个或两个SQL搞定,并且封装的很好。
14 楼 zrq 2007-06-03  
对关键任务的应用程序,不应该使用二级缓存,在集群的情况下,如果使用二级缓存,数据不能保证100%的同步,将会出现不可预测的错误。
象楼主所面对的情况,每天5万的数据量对数据库来说,应该说事小菜一碟。如果性能瓶颈在于数据库,应该在数据库方面考虑,如缓存全表,数据库集群,根据业务需求,优化表的设计。

优化应该从SQL入手,一个订单有8个子对象,一个或两个SQL可以搞定的事,用HIBERNATE 要用几十条SQL,性能当然受影响了。

本人使用HIBERNATE多年,发现它最适合用于单个的save,和 update,对于查询,还是JDBC 最好,当然要经过一些封装。
13 楼 LucasLee 2007-06-03  
OneEyeWolf 写道
Lucas Lee 写道
我个人感觉这样的缓存似乎不会对整体性能有很大的提升.
缓存对于内存的需求倒不是关键.
需要lz实际做出来后仔细测试一下.


 对于整体性能当然有很大的提升了,压力测试时,单个订单页面的频繁的load,对于数据库的压力很小,如果没有缓存,一个订单有8个子对象,加载订单页面时,相当于几十条SQL,DBA肯定要发通报的。


看起来你做过性能测试了.
能否发一下稍微详细点的性能测试对比数据?
12 楼 OneEyeWolf 2007-06-03  
Lucas Lee 写道
我个人感觉这样的缓存似乎不会对整体性能有很大的提升.
缓存对于内存的需求倒不是关键.
需要lz实际做出来后仔细测试一下.


 对于整体性能当然有很大的提升了,压力测试时,单个订单页面的频繁的load,对于数据库的压力很小,如果没有缓存,一个订单有8个子对象,加载订单页面时,相当于几十条SQL,DBA肯定要发通报的。
11 楼 OneEyeWolf 2007-06-03  
lordhong 写道
知道是频繁更新的东西为什么要cache??
直接JDBC就好了.  如果坚持要OO style的话用上iBATIS.


在短时间内,更新频繁,但view的操作更频繁,几乎是更新的至少两倍次数以上,

查看一个订单对象时,实际上几乎要load 订单中的所有信息,包括子类的信息。延迟加载是没有用处的。

使用SQL对于记录查询比较好。对于这种单一记录的load,也解决不了问题。

另外,有的时候。数据库压力会逼你采取很多种组合的手段来减小对数据库的查询,尽可能的缓存一切可以缓存的对象,是最便宜的手段。

  增加硬件,可不是说增加就增加的,直实生活中,作为技术人员除了把程序写好,能够动用的手段是很少的。
10 楼 LucasLee 2007-06-03  
我个人感觉这样的缓存似乎不会对整体性能有很大的提升.
缓存对于内存的需求倒不是关键.
需要lz实际做出来后仔细测试一下.
9 楼 lordhong 2007-06-03  
知道是频繁更新的东西为什么要cache??
直接JDBC就好了.  如果坚持要OO style的话用上iBATIS.
8 楼 OneEyeWolf 2007-06-03  
Lordaeron 写道
每天五萬筆資料, 算是小數目, 但
搞這麼多 ORM的東西了, 請問你們的機器多大?
level 2 cache 吃多少memory?


一个订单在缓存中存活的生命周期,不超过80分钟,很快就被移出了。

5万笔是一天的总单量,在一个时刻,正在流转,尚没有结束的,同时存活在缓存中的订单不过几百个。

这点memory,如果应用服务器都吃不消,还做什么服务器。
7 楼 OneEyeWolf 2007-06-03  
Readonly 写道
这种应用场景下一次需要分配多少个任务,有必要用bulkUpdate这个难用的东东么?




bulkUpdate 是spring封装的方法,真正调用的仍然是Hibernate的

 query.executeUpdate(hsql);方法。

 如果批量更新,又使用Hibernate, 请问用什么方法,hibernate可以明确的清楚发生更新影响的对象?

引用

在核心的订单模块写入缓存??难道你用的缓存支持事务隔离?不怕出人命啊...


 

 应用层缓存,也就是缓存的行为发生应用层,事务是在DAO层完成的,这与事务隔离没有关系,
 更新缓存,一定是在更新数据库成功之后,返回应用层,才进行的操作。

 
6 楼 xly_971223 2007-06-02  
引用
    因为要批量将订单分配给某一个工作人员处理,在代码当中执行了一个bulkUpdate的操作:
  template.bulkUpdate("update order set owner = ? where id in (?, ?,?)");
   
    这时Hibernate会直接将Order对象的二级缓存清楚掉。
    由于二级缓存,总是被刷掉,再查询时,需要重新从数据库Load,所以二级缓存变相直接起的作用很少

用bulkUpdate肯定要清除二级缓存了 否则会导致数据不一致
我觉得最好还是测试一下循环update和bulkUpdate这两者的性能, 在数据多和数据少的时候 差别有多大 然后结合项目实际情况决定用那种那种
在没有测试性能之前 盲目的优化不提倡
5 楼 yjb1981 2007-06-02  
大型的电子商务系统不适合使用Hiberante这些持久层框架,
4 楼 Lordaeron 2007-06-02  
每天五萬筆資料, 算是小數目, 但
搞這麼多 ORM的東西了, 請問你們的機器多大?
level 2 cache 吃多少memory?
3 楼 Readonly 2007-06-02  
这种应用场景下一次需要分配多少个任务,有必要用bulkUpdate这个难用的东东么?

引用

4。在更新数据库时,不是直接清除缓存,而是更新缓存(尽管这有风险),当业务层抛出异常时,才去清空缓存,避免由于频繁更新,而清空缓存。

在核心的订单模块写入缓存??难道你用的缓存支持事务隔离?不怕出人命啊...

相关推荐

Global site tag (gtag.js) - Google Analytics