对于大多数典型的 spring/hibernate 企业应用而言,其性能表现几乎完全依赖于持久层的性能。此篇文章中将介绍如何确认应用是否受数据库约束,同时介绍七种常用的提高应用性能的速成法。本文系 OneAPM 工程师翻译整理。 9 C" M$ G& P( C o4 T3 N4 E; W) R" K
+ W) N7 ^( S9 q
0 T5 H5 ?4 N* [
如何确认应用是否受限于数据库 / y7 C% n* p5 z* b
确认应用是否受限于数据库的第一步,是在开发环境中进行测试,并使用 VisualVM 进行监控。VisualVM 是一款包含在 JDK 中的 java 分析器,在命令行输入 jvisualvm 即可调用。 5 T6 W5 O2 k3 T1 C& R
) `- Y# D q( `- b' Y- ~0 P( P
8 U4 E+ g; }; G" B& K# j2 f0 K
启用 Visual VM 之后,尝试以下步骤: ( c# A8 \: C! u' w; C
( }" N& z* p! k4 L 3 `. n# j( v9 ~* D2 l4 D+ ^
1.双击你正在运行的应用 : d& G" b. A3 p: f2.选择 Sampler " l8 y$ z) a. u% l' L3.点击 Settings 复选框 1 H6 S" E( n0 X5 F7 a4.选择Profile only packages,然后输入下列包: ! [$ Q! t% u0 C- byour.application.packages.* Q: Z9 T: D, c' k
3 _. F3 U1 X, {# u 1 Y. `0 m" ~' ?/ A; c
org.hibernate.* $ W* A; b* ^( A2 P L
" |7 S* X& m) x ) u! U( W$ j( z6 ] p9 m& q" R8 T8 {
org.springframework.* ) j/ l& c) g5 L" e
+ a) G' {; E! ^# Z; Y
8 ~3 h5 j. F. H d8 \' n
your.database.driver.package, 比如 oracle.* ( u4 Z( d* z5 G. e& H+ Y- ` i u! @. `: D I
" U0 _4 w$ D- e/ A9 h5 @6 c9 C" X点击 Sample CPU 1 V m5 N# F! u" K5 U8 L
8 H- | z& E& L0 z+ x - {- v3 w/ A3 B; H# G5 B" i7 C如果应用性能受限于数据库,其 CPU 分析结果会非常恐怖 ( M3 x6 ]3 l& d 3 E5 K& \4 H2 d$ w g0 y2 R2 ? , i" N' Z7 d0 W2 K2 j& `0 _: _% V
Spring/Hibernate 应用性能优化 ; W* f H3 n7 q4 \5 C! } + v1 k. I: @7 k$ C2 N
6 L9 g% B; Z" J9 q2 b; G [
我们看到,客户端 Java 进程花在等待数据库从网络中返回结果的时间占56%。 2 M) i {" _# D G, J# K9 y * e* S: @% |, n @4 G6 ^; i
) d3 t% z0 a, v6 H; G {4 d% U看到数据库查询是导致应用运行缓慢的原因,其实是好兆头。Hibernate 反射调用占比32.7%是正常情况,无法进一步优化。 + U8 k) w. y' m T ; R# Y) ?+ h3 S# g [7 t ) |7 B6 F+ q5 N
性能调优第一步:定义基准运行 2 Z0 Z- \. {/ x8 J# n9 Q
性能调优的第一步是为程序定义基准运行,我们要定义一组能有效执行的输入数据,让程序基准运行与生产环境下的运行差不多。 [5 @7 w1 O5 F' f2 [
" t+ v( t4 p8 \- s* c# T/ D9 Z/ ~2 F* w
; S* h" [. @4 L; n5 ?' ^什么是好的基准? 9 S5 ?" ?0 Z2 Q6 B
好的基准应该具备以下特征: $ F" @0 m8 @% Z$ z' n 9 u Z8 v, M, U a; D2 d
% S5 `' {# \) i* f
功能正确
输入数据的种类与生产环境下相似
在短时间内执行完毕
基准运行的优化方案可以外推至完整运行8 Z6 g2 a0 u5 \8 T
X- U- R+ o+ G7 {/ l$ K定义好的基准是成功解决问题的一半。 ( \* e5 {: z7 W- F B3 A1 d& ~+ c# q3 N( \' r $ k. V- U8 t# J1 {: U9 q+ x
什么是不好的基准 0 I8 S( J8 d* S' h4 D
例如,通过批量运行处理通讯系统的电话数据记录,选取10000条记录就是错误的做法。 " G, d" I- D" R7 `- y8 _, k , C! l, `5 G5 X- U: V6 H i ' Z- R2 A8 }; c$ w1 E! O; O" o0 u
原因是:前10000条记录可能多为语音电话,而未知的性能问题可能发生在短信流量的处理过程中。一开始如果基准不够好,就会导致错误的结论。 % H7 ^2 R5 [" f8 O T% s
, s6 K! S) X) [/ P
d3 O9 }- C$ _7 r
收集 SQL 日志与查询时间 7 R* O4 F; S2 a. G) Z; ]9 e; f
SQL 查询的执行语句与其执行时间可以通过 log4jdbc等方式收集。详细了解如何使用 log4jdbc 收集 SQL 查询信息,点击文章 使用 log4jdbc 优化 Spring/Hibernate 应用 SQL 日志。 - c1 _7 p/ K4 v 2 U3 a& P; Z) {6 a8 X* M
2 _8 l, Q3 j6 J
查询的执行时间是从 Java 客户端收集的,该时间包含查询数据库的来回网络调用。SQL 查询的日志如下: $ g, n Q; w' ?# \' \3 q ) S. V' ?5 B9 e8 H+ B + v U' V4 ]+ B, m
16 avr. 2014 11:13:48 | SQL_QUERY /* insert your.package.YourEntity */ insert into YOUR_TABLE (...) values (...) {executed in 13 msec} 8 v5 R$ P! p; P" V- \预处理语句也是很重要的信息来源,它们常常会透露出常用的查询类型。了解更多的日志讯息,可以查看文章:Hibernate 为什么/在何处使用该 SQL 查询? - b$ X( g$ u5 x- u0 x4 r9 w ' T5 q- R8 F# }8 ]7 J, ?5 w 5 _. ]5 y6 c Z/ v* N4 l* J
通过 SQL 日志可以了解哪些指标? " [* b. y) S0 I2 A# q/ w1 h' ]% eSQL 日志可以回答下列问题: + u+ T5 M" l# j& m# B 9 S6 i+ \0 v9 H m! `9 R0 F
+ w5 K$ y! a% t6 `( P6 d* N2 u
哪些是执行过的最慢查询?
哪些是最常用的查询?
生成主键的耗时是多少?
是否有数据适合缓存?4 ?, \. v1 s% J
* ] C* [; w" t4 G9 l
如何解析 SQL 日志 2 o2 n. x0 r L& `) \
对于大量的日志文件,最可行的解析方式就是使用命令行工具,该方法的好处是非常灵活,只要写一小段脚本或命令,我们可以抽取出几乎大多数指标。只要你喜欢,任何命令行工具都适用。 " T; X% S2 x; K3 V6 Y1 S$ m( h
3 C5 K; w, ?0 p/ Y$ n; j, g4 y
& V5 l4 j9 ^9 o+ m' ]6 x$ r
如何你习惯了 Unix 命令行,bash 或是一个好选择。Bash 也可以在 Windows 工作站使用,Cygwin 或 Git 都包含了 bash 命令行。 2 q' V ?1 a/ Y% ~
. D6 ]* @0 M1 ], N5 {. u & Y( g. n7 s4 a8 n7 L$ {
常用的速成法 " v3 Z' n* Y$ b, {下面介绍的速成法能找出 Spring/Hibernate 应用中常见的性能问题,以及对应的解决方案。 * A8 K4 \* C1 }9 V( i. X: W7 K
4 k4 r. e/ K* w
; ~! C6 Y6 ~# O. F: G速成法1——减少生成主键的代价 2 R' p4 T6 G3 n# r* H9 f
在插入操作频繁的进程中,主键的生成策略很重要。生成 id 的一种常见方法是使用数据库序列,通常一张表一个 id,从而避免在不同表间进行插入时的冲突。 , g5 T$ \ u) J7 B) G/ `
" L2 i- C. K5 j3 r0 o2 M$ ^3 k 0 y6 v2 z9 O0 [问题在于,如果要插入50条记录,我们希望为了获取这50个 id,可以避免50趟查询数据库的来回网络调用,让 Java 进程不一直等待。 ( Z8 A: V5 y1 y7 D
: L2 n% i7 H Z$ n* m2 P" n) I / x" f v: e7 W3 Z
Hibernate 通常如何解决此问题? ' F8 `4 X$ C* R5 l7 K# L9 H' X
Hibernate 提供了优化的 ID 生成器以避免此问题。也即,对于序列,会默认使用 HiLo id 生成器。以下是 HiLo 序列生成器的工作方式: $ E9 b' H8 {! j+ m! Y7 U + H* W. m/ Q; L, L8 R2 \+ Y
3 a) ~# g/ Y& F/ y+ h+ ^