首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件架构设计 >

【转载】hibernate性能调优(下)

2012-11-17 
【转载】hibernate性能调优(上)原文:http://xiexiejiao.cn/hibernate/hibernate-performance-optimization-a

【转载】hibernate性能调优(上)

原文:http://xiexiejiao.cn/hibernate/hibernate-performance-optimization-a.html

?

Hibernate是笔者使用了超过5年的优秀ORM框架,虽然说使用了5年,但笔者并没有把握说自己真正意义上的精通Hibernate。说道熟悉Hibernate还差不多,因为Hibernate用法和特性只要使用过或许都很简单,但是要做到发挥Hibernate最大限度的潜力,Hibernate优化,或者说Hibernate性能优化笔者仅仅是初窥门径而已。这里摘录一篇牛人对于Hibernate优化的文章,希望对自己以后的使用指引下方向吧。

1. Hibernate优化-引言

Hibernate是最流行的对象关系映射(ORM)引擎之一,它提供了数据持久化和查询服务。

在你的项目中引入Hibernate并让它跑起来是很容易的。但是,要让它跑得好却是需要很多时间和经验的。

通过我们的使用Hibernate 3.3.1和Oracle 9i的能源项目中的一些例子,本文涵盖了很多Hibernate调优技术。其中还提供了一些掌握Hibernate调优技术所必需的数据库知识。

我们假设读者对Hibernate有一个基本的了解。如果一个调优方法在Hibernate 参考文档(下文简称HRD)或其他调优文章中有详细描述,我们仅提供一个对该文档的引用并从不同角度对其做简单说明。我们关注于那些行之有效,但又缺乏文档的调优方法。

2. Hibernate优化-Hibernate性能调优

调优是一个迭代的、持续进行的过程,涉及软件开发生命周期(SDLC)的所有阶段。在一个典型的使用Hibernate进行持久化的Java?EE应用程序中,调优会涉及以下几个方面:

    业务规则调优设计调优Hibernate调优Java GC调优应用程序容器调优底层系统调优,包括数据库和OS。

    没有一套精心设计的方案就去进行以上调优是非常耗时的,而且很可能收效甚微。好的调优方法的重要部分是为调优内容划分优先级。可以用Pareto定律(又称“80/20法则”)来解释这一点,即通常80%的应用程序性能改善源自头20%的性能问题[5]。

    相比基于磁盘和网络的访问,基于内存和CPU的访问能提供更低的延迟和更高的吞吐量。这种基于IO的Hibernate调优与底层系统IO部分的调优应该优先于基于CPU和内存的底层系统GC、CPU和内存部分的调优。

    范例1

    我们调优了一个选择电流的HQL查询,把它从30秒降到了1秒以内。如果我们在垃圾回收方面下功夫,可能收效甚微——也许只有几毫秒或者最多几秒,相比HQL的改进,GC方面的改善可以忽略不计。

    好的调优方法的另一个重要部分是决定何时优化[4]。

    积极优化的提倡者主张开始时就进行调优,例如在业务规则和设计阶段,在整个SDLC都持续进行优化,因为他们认为后期改变业务规则和重新设计代价太大。

    另一派人提倡在SDLC末期进行调优,因为他们抱怨前期调优经常会让设计和编码变得复杂。他们经常引用Donald Knuth的名言“过早优化是万恶之源”?[6]。

    为了平衡调优和编码需要一些权衡。根据笔者的经验,适当的前期调优能带来更明智的设计和细致的编码。很多项目就失败在应用程序调优上,因为上面提到的“过早优化”阶段在被引用时脱离了上下文,而且相应的调优不是被推迟得太晚就是投入资源过少。

    但是,要做很多前期调优也不太可能,因为没有经过剖析,你并不能确定应用程序的瓶颈究竟在何处,应用程序一般都是这样演化的。

    对我们的多线程企业级应用程序的剖析也表现出大多数应用程序平均只有20-50%的CPU使用率。剩余的CPU开销只是在等待数据库和网络相关的IO。

    基于上述分析,我们得出这样一个结论,结合业务规则和设计的Hibernate调优在Pareto定律中20%的那个部分,相应的它们的优先级更高。

    一种比较实际的做法是:

      识别出主要瓶颈,可以预见其中多数是Hibernate、业务规则和设计方面的(其数量视你的调优目标而定;但三到五个是不错的开端)。修改应用程序以便消除这些瓶颈。测试应用程序,然后重复步骤1,直到达到你的调优目标为止。

    你能在Jack Shirazi的《Java Performance Tuning》?[7]一书中找到更多关于性能调优阶段的常见建议。

    下面的章节中,我们会按照调优的大致顺序(列在前面的通常影响最大)去解释一些特定的调优技术。

    3. Hibernate优化-监控和剖析

    没有对Hibernate应用程序的有效监控和剖析,你无法得知性能瓶颈以及何处需要调优。

    3.1.1 监控SQL生成

    尽管使用Hibernate的主要目的是将你从直接使用SQL的痛苦中解救出来,为了对应用程序进行调优,你必须知道Hibernate生成了哪些 SQL。JoeSplosky在他的《The Law of Leaky Abstractions》一文中详细描述了这个问题。

    你可以在log4j中将org.hibernate.SQL包的日志级别设为DEBUG,这样便能看到生成的所有SQL。你还可以将其他包的日志级别设为DEBUG,甚至TRACE来定位一些性能问题。

    3.1.2 查看Hibernate统计

    如果开启hibernate.generate.statistics,Hibernate会导出实体、集合、会话、二级缓存、查询和会话工厂的统计信息,这对通过SessionFactory.getStatistics()进行的调优很有帮助。为了简单起见,Hibernate还可以使用MBean“org.hibernate.jmx.StatisticsService”通过JMX来导出统计信息。你可以在这个网站找到配置范例?。

    3.1.3 剖析

    一个好的剖析工具不仅有利于Hibernate调优,还能为应用程序的其他部分带来好处。然而,大多数商业工具(例如JProbe?[10])都很昂贵。幸运的是Sun/Oracle的JDK1.6自带了一个名为“Java VisualVM”?[11]的调试接口。虽然比起那些商业竞争对手,它还相当基础,但它提供了很多调试和调优信息。

    4. Hibernate优化-调优技术

    4.1 业务规则与设计调优

    尽管业务规则和设计调优并不属于Hibernate调优的范畴,但此处的决定对后面Hibernate的调优有很大影响。因此我们特意指出一些与Hibernate调优有关的点。

    在业务需求收集与调优过程中,你需要知道:

      数据获取特性包括引用数据(reference data)、只读数据、读分组(read group)、读取大小、搜索条件以及数据分组和聚合。数据修改特性包括数据变更、变更组、变更大小、无效修改补偿、数据库(所有变更都在一个数据库中或在多个数据库中)、变更频率和并发性,以及变更响应和吞吐量要求。数据关系,例如关联(association)、泛化(generalization)、实现(realization)和依赖(dependency)。

      基于业务需求,你会得到一个最优设计,其中决定了应用程序类型(是OLTP还是数据仓库,亦或者与其中某一种比较接近)和分层结构(将持久层和服务 层分离还是合并),创建领域对象(通常是POJO),决定数据聚合的地方(在数据库中进行聚合能利用强大的数据库功能,节省网络带宽;但是除了像 COUNT、SUM、AVG、MIN和MAX这样的标准聚合,其他的聚合通常不具有移植性。在应用服务器上进行聚合允许你应用更复杂的业务逻辑;但你需要 先在应用程序中载入详细的数据)。

      范例2

      分析员需要查看一个取自大数据表的电流ISO(Independent System Operator)聚合列表。最开始他们想要显示大多数字段,尽管数据库能在1分钟内做出响应,应用程序也要花30分钟将1百万行数据加载到前端UI。经 过重新分析,分析员保留了14个字段。因为去掉了很多可选的高聚合度字段,从剩下的字段中进行聚合分组返回的数据要少很多,而且大多数情况下的数据加载时 间也缩小到了可接受的范围内。

      范例3

      过24个“非标准”(shaped,表示每小时都可以有自己的电量和价格;如果所有24小时的电量和价格相同,我们称之为“标准”)小时会修改小时电流交易,其中包括2个属性:每小时电量和价格。起初我们使用Hibernate的select-before-update特性,就是更新24行数据需要24次选择。因为我们只需要2个属性,而且如果不修改电量或价格的话也没有业务规则禁止无效修改,我们就关闭了select-before-update特性,避免了24次选择。

      4.2继承映射调优

      尽管继承映射是领域对象的一部分,出于它的重要性我们将它单独出来。HRD?[1]中的第9章“继承映射”已经说得很清楚了,所以我们将关注SQL生成和针对每个策略的调优建议。

      以下是HRD中范例的类图:

      type="integer"/>

      这种隐式关联避免了数据库表连接和额外的字段选择,降低了数据传输的大小。

      4.4 连接池调优

      由于创建物理数据库连接非常耗时,你应该始终使用连接池,而且应该始终使用生产级连接池而非Hibernate内置的基本连接池算法。

      通常会为Hibernate提供一个有连接池功能的数据源。Apache DBCP的BasicDataSource[13]是一个流行的开源生产级数据源。大多数数据库厂商也实现了自己的兼容JDBC 3.0的连接池。举例来说,你也可以使用Oracle ReaApplication Cluster?[15]提供的JDBC连接池[14]以获得连接的负载均衡和失败转移。

      不用多说,你在网上能找到很多关于连接池调优的技术,因此我们只讨论那些大多数连接池所共有的通用调优参数:

        最小池大小:连接池中可保持的最小连接数。最大池大小:连接池中可以分配的最大连接数。
        如果应用程序有高并发,而最大池大小又太小,连接池就会经常等待。相反,如果最小池大小太大,又会分配不需要的连接。最大空闲时间:连接池中的连接被物理关闭前能保持空闲的最大时间。最大等待时间:连接池等待连接返回的最大时间。该参数可以预防失控事务(runaway transaction)。验证查询:在将连接返回给调用方前用于验证连接的SQL查询。这是因为一些数据库被配置为会杀掉长时间空闲的连接,网络或数据库相关的异常也可能会杀死连接。为了减少此类开销,连接池在空闲时会运行该验证。

        4.5事务和并发的调优

        短数据库事务对任何高性能、高可扩展性的应用程序来说都是必不可少的。你使用表示对话请求的会话来处理单个工作单元,以此来处理事务。

        考虑到工作单元的范围和事务边界的划分,有3中模式:

          每次操作一个会话。每次数据库调用需要一个新会话和事务。因为真实的业务事务通常包含多个此类操作和大量小事务,这一般会引起更多数据库活动(主要是数据库每次提交需要将变更刷新到磁盘上),影响应用程序性能。这是一种反模式,不该使用它。使用分离对象,每次请求一个会话。每次客户端请求有一个新会话和一个事务,使用Hibernate的“当前会话”特性将两者关联起来。
          在一个多层系统中,用户通常会发起长对话(或应用程序事务)。大多数时间我们使用Hibernate的自动版本和分离对象来实现乐观并发控制和高性能。带扩展(或长)会话的每次对话一会话。在一个也许会跨多个事务的长对话中保持会话开启。尽管这能把你从重新关联中解脱出来,但会话可能会内存溢出,在高并发系统中可能会有旧数据。

          你还应该注意以下几点。

            如果不需要JTA就用本地事务,因为JTA需要更多资源,比本地事务更慢。就算你有多个数据源,除非有跨多个数据库的事务,否则也不需要 JTA。在最后的一个场景下,可以考虑在每个数据源中使用本地事务,使用一种类似“Last Resource Commit Optimization”[16]的技术(见下面的范例6)。如果不涉及数据变更,将事务标记为只读的,就像4.3.1提到的那样。总是设置默认事务超时。保证在没有响应返回给用户时,没有行为不当的事务会完全占有资源。这对本地事务也同样有效。如果Hibernate不是独占数据库用户,乐观锁会失效,除非创建数据库触发器为其他应用程序对相同数据的变更增加版本字段值。

            范例6

            我们的应用程序有多个在大多数情况下只和数据库“A”打交道的服务层方法;它们偶尔也会从数据库“B”中获取只读数据。因为数据库“B”只提供只读数据,我们对这些方法在这两个数据库上仍然使用本地事务。

            服务层上有一个方法设计在两个数据库上执行数据变更。以下是伪代码:

            //Make sure a local transaction on database A exists@Transactional (readOnly=false, propagation=Propagation.REQUIRED)public void saveIsoBids() {  //it participates in the above annotated local transaction  insertBidsInDatabaseA();  //it runs in its own local transaction on database B   insertBidRequestsInDatabaseB(); //must be the last operation 

            因为insertBidRequestsInDatabaseB()是saveIsoBids ()中的最后一个方法,所以只有下面的场景会造成数据不一致:

            在saveIsoBids()执行返回时,数据库“A”的本地事务提交失败。

            但是,就算saveIsoBids()使用JTA,在两阶段提交(2PC)的第二个提交阶段失败的时候,你还是会碰到数据不一致。因此如果你能处理好上述的数据不一致性,而且不想为了一个或少数几个方法引入JTA的复杂性,你应该使用本地事务。

            关于作者

            Yongjun Jiao是SunGard Consulting Services的技术主管。过去10年中他一直是专业软件开发者,他的专长包括Java SE、Java EE、Oracle和应用程序调优。他最近的关注点是高性能计算,包括内存数据网格、并行计算和网格计算。

            Stewart Clark是SunGard Consulting Services的负责人。过去15年中他一直是专业软件开发者和项目经理,他的专长包括Java核心编程、Oracle和能源交易。
            摘自:infoq
            英文地址:http://www.infoq.com/articles/hibernate_tuning

热点排行