【译】如何用Lightrun堆Java应用程序进行性能调优转载
在本文中,我将向你展示如何使用Lightrun分析 Java 应用程序,以便发现可以应用于当前 Java 程序的各种性能调整的建议。
在上一篇文章中,我解释了 Lightrun 是什么以及如何使用它来注入动态日志、捕获运行时快照或添加动态指标。
DefaultLoadEventListener
使用 Hibernate 获取 JPA 实体时,LoadEvent
会触发 a,由 处理DefaultLoadEventListener
,如下所示:
将DefaultLoadEventListener
检查实体是否位于当前 JPA Persistence Context 或一级缓存中。如果在那里找到实体,则将返回相同的 Object 引用。
这意味着两个连续的实体获取调用将始终返回相同的 JavaObject
引用。这就是 JPA 和 Hibernate 提供应用程序级可重复读取的原因。
如果在一级缓存中找不到实体,当且仅当二级缓存启用时,Hibernate 才会尝试从二级缓存中加载它。
最后,如果实体无法从任何缓存加载,它将从数据库加载。
现在,这个过程可以在调用时发生EntityManager.find
,在遍历关联时发生,或者间接地发生在FetchType.EAGER
策略中。
检查 N+1 查询问题
工具在测试期间非常有用,但对于必须第一次检查生产系统的顾问来说不太实用。
例如,让我们以Spring PetClinic应用程序为例:
@Entity
@Table(name = "pets")
public class Pet extends NamedEntity {
@Column(name = "birth_date")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthDate;
@ManyToOne
@JoinColumn(name = "type_id")
private PetType type;
@ManyToOne
@JoinColumn(name = "owner_id")
private Owner owner;
}
该Pet
实体有两个父关联,type
和 owner
,每个都使用注释进行@ManyToOne
注释。但是,默认情况下,@ManyToOne
关联使用FetchType.EAGER
获取策略。
因此,如果我们加载 2 个Pet
实体同时还获取它们的关联owner
关联:
List<Pet> pets = entityManager.createQuery("""
select p
from Pet p
join fetch p.owner
where p.id in :petIds
""")
.setParameter("petIds", List.of(3L, 6L))
.getResultList();
Hibernate 将执行 3 个查询:
SELECT
p.id as id1_1_1_,
p.name as name2_1_1_,
p.birth_date as birth_da3_1_1_,
p.owner_id as owner_id4_1_1_,
p.type_id as type_id5_1_1_,
o.id as id1_0_0_,
o.first_name as first_na2_0_0_,
o.last_name as last_nam3_0_0_,
o.address as address4_0_0_,
o.city as city5_0_0_,
o.telephone as telephon6_0_0_
FROM
pets p
JOIN
owners o ON o.id = p.owner_id
WHERE
p.id IN (3, 6)
SELECT
pt.id as id1_3_0_,
pt.name as name2_3_0_
FROM
types pt
WHERE
pt.id = 3
SELECT
pt.id as id1_3_0_,
pt.name as name2_3_0_
FROM
types pt
WHERE
pt.id = 6
那么,为什么执行了 3 个查询而不是只有 1 个呢?这就是臭名昭著的N+1 查询问题。
使用 Lightrun 进行 Java 性能调优
虽然你可以使用集成测试检测 N+1 查询问题,但有时你无法做到这一点,因为被聘请分析的系统已部署到生产环境中,而你看不到源代码。
在这种情况下,像Lightrun这样的工具会变得非常方便,因为它可以简单地动态注入运行时快照,该快照仅在满足给定条件时才记录下来。
第一步是在Hibernate 类的loadFromDatasource
方法中添加运行时快照。DefaultLoadEventListener
请注意,快照仅记录isAssociationFetch()
了关联LoadEvent
返回的方法true
。此条件允许我们捕获由 N+1 查询问题执行的辅助查询。
现在,当加载姓氏为 Davis 的所有宠物主人时,PetClinic 应用程序执行以下 SQL 查询:
SELECT DISTINCT
o.id AS id1_0_0_,
p.id AS id1_1_1_,
o.first_name AS first_na2_0_0_,
o.last_name AS last_nam3_0_0_,
o.address AS address4_0_0_,
o.city AS city5_0_0_,
o.telephone AS telephon6_0_0_,
p.name AS name2_1_1_,
p.birth_date AS birth_da3_1_1_,
p.owner_id AS owner_id4_1_1_,
p.type_id AS type_id5_1_1_,
p.owner_id AS owner_id4_1_0__,
p.id AS id1_1_0__
FROM
owners o
LEFT OUTER JOIN
pets p ON o.id=p.owner_id
WHERE
o.last_name LIKE 'Davis%'
SELECT
pt.id as id1_3_0_,
pt.name as name2_3_0_
FROM
types pt
WHERE
pt.id = 6
SELECT
pt.id as id1_3_0_,
pt.name as name2_3_0_
FROM
types pt
WHERE
pt.id = 3
并且在查看 Lightrun Snapshot 控制台时,我们可以看到已经注册了两条记录:
第一个快照如下所示:
第二个快照如下所示:
请注意,由于FetchType.EAGER
策略的广泛使用,这两个快照对应于 Spring Petclinic 应用程序执行的辅助查询。
很酷,对吧?
作者:Vlad Mihalcea