性能文章>【译】如何用Lightrun堆Java应用程序进行性能调优>

【译】如何用Lightrun堆Java应用程序进行性能调优转载

2年前
338927

在本文中,我将向你展示如何使用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

Lightrun 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 控制台时,我们可以看到已经注册了两条记录:

第一个快照如下所示:

Lightrun DefaultLoadEventListener 第一个结果

第二个快照如下所示:

Lightrun DefaultLoadEventListener 第二个结果

请注意,由于FetchType.EAGER策略的广泛使用,这两个快照对应于 Spring Petclinic 应用程序执行的辅助查询。

很酷,对吧?

作者:Vlad Mihalcea

点赞收藏
金色梦想

终身学习。

请先登录,查看2条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

JDBC PreparedStatement 字段值为null导致TBase带宽飙升的案例分析

JDBC PreparedStatement 字段值为null导致TBase带宽飙升的案例分析

7
2