专栏名称: ImportNew
伯乐在线旗下账号,专注Java技术分享,包括Java基础技术、进阶技能、架构设计和Java技术领域动态等。
目录
相关文章推荐
芋道源码  ·  滴滴打车如何找出方圆一千米内的乘客?揭开 ... ·  22 小时前  
芋道源码  ·  ES+MySQL优雅的实现模糊搜索 ·  22 小时前  
芋道源码  ·  SpringBoot 项目热部署的3种方式 ·  22 小时前  
Java编程精选  ·  患者带着DeepSeek来看病,医学博主自嘲 ... ·  3 天前  
芋道源码  ·  Spring AI + ... ·  昨天  
51好读  ›  专栏  ›  ImportNew

Spring / Hibernate 应用性能调优

ImportNew  · 公众号  · Java  · 2017-03-16 20:09

正文

(点击 上方公众号 ,可快速关注)


来源:ImportNew - 陈晓舜

如有好文章投稿,请点击 → 这里了解详情


对大部分典型的Spring/Hibernate企业应用来说,应用的性能大部分由持久层的性能决定。


这篇文章会重温一下怎么去确认我们的应用是否是”数据库依赖(data-bound)”(译者注:即非常依赖数据库,大量时间花在数据库操作上),然后会大概过一下7个常用的提升应用性能的速效方案。


怎么确定应用是否是“数据库依赖”


确认一个应用是是否是数据库依赖,首先通过在一些开发环境中做基本的运行,可以使用VisualVM来进行监控。VisualVM是一个和JDK一起发布的Java性能调优器,可以通过命令行jvisualvm运行。


执行Visual VM后,尝试下面的步骤:


  1. 双击你正在运行的应用

  2. 选择抽样器(Sampler)

  3. 点击设置复选框

  4. 选择只调优包,并且限定如下的包类型:

  • 你的应用程序包

  • org.hibernate.*

  • org.springframework.*

  • 你的数据库jar包名,如oracle.*

  • 点击抽样(Sample) CPU


CPU抽样一个典型的“数据库依赖”应用将会得到类似下面的结果:



我们可以看到Java客户端进程花费了56%的时间在等待数据库从网络中返回结果。


这是一个很好的标志,表示正是数据库查询造成了应用的缓慢。Hibernate反射调用占了32.7%是正常,而且我们对此也无能为力。


性能调优第一步 —— 得到基准运行值(baseline run)


性能调优的第一步是为程序定义一个基准运行值。我们需要一系列可以使程序运行的有效输入数据,它必须跟在生产环境运行类似。


最主要的区别是基准运行需要在更短的时间内运行完成,比较理想的指导值是执行时间为5-10分钟。


什么是好的基准(baseline)?


一个好的基准需要有下面的特性:



取一个好的基准可以解决一大半的问题。


什么是不好的基准


例如,在一个批处理运行的执行电话数据记录的电信系统中,取得前10000条记录会是一个错误的做法。


原因是:前10000条有可能大部分是语音电话,但未知的性能问题却是在处理短信通道(SMS traffic)。在一个大批量执行的过程中获取前面的一些记录不是一个好的基准,有可能会得到错误的结论。


收集SQL日志和查询时间


SQL查询和执行时间可以使用如log4jdbc来进行收集。可以看这篇博客关于如何使用log4jdbc来收集SQL查询 —— 通过log4jdbc来改进Spring/Hibernate的SQL日志(http://blog.jhades.org/logging-the-actualreal-sql-queries-of-a-springhibernate-application/).


查询执行时间是在Java客户端进行计算的,它包含了到数据库的网络往返请求耗时。SQL查询日志看起来就像这样:


16 avr. 2014 11:13:48 | SQL_QUERY /* insert your.package.YourEntity */ insert into YOUR_TABLE (...) values (...) {executed in 13 msec}


Prepared statements自己也是很好的信息源——它允许识别经常执行的查询类型。根据这篇博客,可以很简单地记录——Hibernate在哪里,为什么做这个SQL查询(http://blog.jhades.org/how-to-find-out-why-hibernate-is-doing-a-certain-sql-query/)。


SQL日志可以得到什么数据


SQL日志可以回答这些问题:



怎么转换SQL日志


也许对于大日志文件最可行的方案就是使用命令行工具。这个方法的优点是比较灵活。


只需要耗费点时间写一小段脚本或命令,我们可以抽取大部分任何需要的数据。任何命令行都可以按你喜欢的方式去使用。


如果你使用Unix命令行,bash会是一个很好的选择。Bash也可以在Windows工作站中使用,使用例如Cygwin或Git这些包含bash命令行的工具。


常用的速效方案


下面的速效方案可以识别Spring/Hibnerate应用中的常见性能问题和对应的解决方案。


速效方案1 —— 减少主键提前生成


在一些插入密集(intert-intensive)的处理中,主键生成策略的选择有很大的影响。一个常见的生成ID的方法是使用数据库的序列(sequences),通常每个表一个,以避免插入不同表时的冲突。


问题在于,如果插入50条记录,我们希望可以避免50次通过数据库获取50个ID的网络往返,而不使Java进程在大部分时间内等待。


Hibernate通常是怎么处理这个的?


Hibernate提供了新优化的ID生成器可以避免这个问题。对于sequences,会默认使用一个HiLo id生成器。HiLo序列生成器的工作过程如下:



所以从第一次sequence调用时,就已经生成了50个key了,减少了大量的网络往返耗时。


这些新优化的主键生成器在Hibernate4中是默认开启的,在需要时,可以通过设置hibernate.id.new_generator_mappings为false进行关闭。


为什么主键生成仍然是个问题?


问题在于,如果你定义主键生成策略为AUTO,优化生成器仍然是关闭的,你的应用仍然还是会进行很大数量的sequence调用。


为了保证新的优化生成器被启用,确保使用SEQUENCE策略而不是AUTO:


@Id

@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "your_key_generator")

private Long id;


有了这个小改变,’插入密集’的应用会有10%-20%的提升,而并不需要做其他的代码修改。


速效方案2 —— 使用JDBC批量插入/修改


对于批量的程序,JDBC驱动通常会提供称之为’JDBC批量插入/修改’的优化方案用于减少网络往返消耗。在使用它们时,插入/修改在发送到数据库前会在驱动层排队(译者注:达到一定的数量后会一次性发送多条SQL进行执行)。


当指定的阀值达到后,队列中的批量语句将会被一次性发送到数据库。这防止了驱动一个接一个的发送请求,浪费多个网络请求。


下面是用于启用批量插入/更新的entity manager factory的配置:


100

true

true


只是设置JDBC batch size不会生效。这是因为JDBC驱动只有在具体某个相同的表接收到插入/更新时才会把插入当成批量处理。


如果接收到对一个新表的插入命令,JDBC驱动会在执行新表的批量语句前先送出上一个表的批量的语句。


使用Spring Batch时也有隐晦地使用到一个类似的功能。这个优化可以很简单地为你的“插入密集”应用节省30%到40%的时间,而不需要修改一行代码。


速效方案3 —— 定期刷新和清空Hibernate session


当添加/修改数据库数据时,为了防止它们在session关闭后被重新修改,Hibnerate会在session中保持已经持久化的实体的版本。


但很多时候,在插入数据库完成后,我们可以安全地丢弃实体。这可以在Java客户端释放内存,防止由于长时间运行Hibernate session造成的性能问题。


这种长时间运行的session应该被尽量避免,但如果由于某些原因确实需要使用,下面的代码展示了怎么继续保存内存引用:


entityManager.flush();

entityManager.clear();


这个flush会触发发送操作,新实体的插入操作会被立刻发送到数据库。clear会从session中释放新实体。







请到「今天看啥」查看全文