vlambda博客
学习文章列表

读书笔记《hands-on-high-performance-with-spring-5》应用程序性能优化

Application Performance Optimization

在上一章中,我们重点介绍了如何分析应用程序以找出应用程序的性能问题。我们还介绍了日志记录,它是识别应用程序问题的有用工具。当我们处理 Spring 应用程序时,它是必不可少的部分,并将成为我们日常工作的一部分。

现在让我们看看我们在本章中得到了什么。这是本书的关键一章;它为您提供了提高应用程序性能的方法。在本章中,我们将讨论应用程序性能优化的基本方法,这是任何应用程序的关键,包括基于 Spring 的应用程序。我们将讨论 Spring 对 Java Management Extension (JMX) 的支持、数据库交互的改进以及 Spring 应用程序的性能调整。在本章结束时,您将能够识别基于 Spring 的应用程序中的性能瓶颈并解决它们。

让我们看看使用结构化方法优化应用程序性能的重要方面。我们将涵盖以下主题:

  • Performance issue symptoms
  • Performance tuning life cycle
  • Performance tuning patterns and anti-patterns
  • The iterative performance-tuning process
  • Spring support of JMX

Performance issue symptoms

让我们从性能问题症状开始。这是一个显而易见的起点,因为这就像咨询医生,讨论症状然后做出诊断。应用程序性能是最终用户在速度、交付内容的准确性和最高负载下的平均响应时间方面所经历的行为。负载是指应用程序每单位时间处理的事务数。响应时间是应用程序在这种负载下响应用户操作所需的时间。

每当性能需要改进时,首先想到的是影响我们应用程序性能的问题。要找到性能问题,我们需要寻找可能导致我们遇到问题的某些症状。

在 Spring 应用程序中可以观察到的一些常见症状如下:

  • Timeouts
  • Running out of worker threads
  • Threads waiting on class loaders
  • A major amount of time spent on loading classes even under normal load
  • Class loader attempts to load non-existing classes

在以下部分中,我们将通过示例上下文了解这些症状。详细信息将帮助我们在症状发生时识别它。

Timeouts

超时以两种不同的方式发生。一是请求超时,用HTTP响应状态码408表示。另一种超时方式是网关超时,由 HTTP 响应状态码 504 表示。

请求超时表示服务器在指定时间内没有收到来自客户端的完整请求。在这种情况下,服务器选择关闭与客户端的连接。请求超时是直接来自服务器的错误消息。

网关超时表示网关或代理服务器在处理请求时超时。在大多数情况下,这是因为代理或网关没有收到来自上游实际服务器的及时响应。

Running out of worker threads

以银行为例;该银行有一个 Web 应用程序,上面有一个监控系统。监控系统密切关注 JVM 的强度。测量的参数是内存、CPU、I/O、堆内存和其他各种属性。监控系统提供了独特的仪表板,显示并突出显示上述属性的测量值。随附的仪表板演示了在银行应用程序中执行的一组活动。此仪表板还识别 JVM 在访问专用应用程序资源(例如线程)时开始运行不足的活动组。该应用程序在多个 JVM 环境中运行。以下是示例仪表板的屏幕截图,以供参考:

读书笔记《hands-on-high-performance-with-spring-5》应用程序性能优化

监控系统配置有阈值。例如,JVM 一次使用的最大线程数不应超过 250。当 JVM 一次使用的线程数少于 150 时,仪表板中相应的 JVM 指示器为绿色。当 JVM 开始使用超过 150 个线程时,监控系统将 JVM 指示为红色。这是一种症状,表明可能会发生故障或性能受到超出正常范围的影响。

以下是一个基于时间线的屏幕截图,显示了 JVM 的工作线程已达到最大值:

读书笔记《hands-on-high-performance-with-spring-5》应用程序性能优化

Threads waiting on class loaders

继续上一节中描述的相同示例,出现的第一个问题是,这些线程有什么问题?深入查看线程并分解状态,发现这些线程(大约 250 个线程中的 242 个)正在寻找服务器的 CompoundClassLoader .这些线程正在堆叠额外的对象,这就是他们寻找类加载器的原因。 由于大量线程试图访问该公共资产(类加载器),大多数线程都陷入了暂停。

以下监控截图显示等待的线程数 CompoundClassLoader

读书笔记《hands-on-high-performance-with-spring-5》应用程序性能优化

Time spent on class-loading activities

监控系统分析得出的另一件事是线程将大部分时间花在类加载活动上。以下是突出显示这一点的监控系统屏幕截图:

读书笔记《hands-on-high-performance-with-spring-5》应用程序性能优化

查看之前的监控系统截图,很明显无论当前负载如何,在请求处理生命周期中,类加载活动与其他活动相比都需要相当长的时间。这是性能问题的迹象或症状,因为它会增加整体响应时间。对于银行,可以通过评估平均响应时间来确认。

Class loader trying to load non-existent classes

出现了一个问题:类堆叠非常重要吗?深入挖掘并查看处理的请求,它表明每个请求都试图堆叠一个不存在的类。应用程序服务器提示了大量的 ClassNotFoundException 类。问题的主要驱动因素是该类永远无法有效地堆叠,但应用程序服务器继续尝试为每个请求堆叠它。对于快速和适度的请求和功能,这应该不是问题。每个传入请求或功能的这种详细程度可能会抓住稀有资产 - 类加载器 - 并因此影响请求的响应时间。

监控系统的能力、适应性和容量是利用堆叠类的数据捕捉每一个请求和响应,以帮助识别症状。以下屏幕截图显示了应用程序框架中的一个这样的场景:

读书笔记《hands-on-high-performance-with-spring-5》应用程序性能优化

潜在性能问题的症状现在必须清楚。它特别适用于任何基于 JVM 的 Web 应用程序,而不仅仅是基于 Spring 的 Web 应用程序。以下屏幕截图向我们展示了基本上可以帮助我们识别性能问题影响的指针:

读书笔记《hands-on-high-performance-with-spring-5》应用程序性能优化

性能不佳的应用程序对企业来说很重要,因为他们已经看到由于应用程序性能而导致销售额下降。由于性能问题,应用程序还可能注意到生产力或业务损失。

让我们通过一个基本的说明来了解性能问题对业务的影响:

读书笔记《hands-on-high-performance-with-spring-5》应用程序性能优化

从上图中我们可以理解,不良的应用程序行为会影响业务,这可以描述为项目成本高、转化率下降、重复访问减少和客户保留率低、销售额下降、生产力下降、客户流失、项目成本增加、利润和投资回报延迟或下降。性能对企业来说很重要。

我们需要做些什么来避免或解决性能问题?不要等待性能问题发生。审查架构、设计和代码,并提前规划负载测试、调优和基准测试。今天,在竞争激烈的营销世界中,组织的关键是让他们的系统以最佳性能启动和运行。任何故障或停机都会直接影响业务和收入;应用程序的性能是一个不容忽视的因素。由于技术以多种方式广泛使用,海量的数据日益增长。正因为如此,负载平均值正在飙升。在某些情况下,无法保证数据不会超过限制或用户数不会超出限制。

在任何时候,我们都可以满足意想不到的扩展需求。对于任何组织而言,其应用程序提供可扩展性、性能、可用​​性和安全性都非常重要。通过扩展数据库以满足跨多个服务器的不同应用程序查询,在水平和垂直扩展方面的应用程序可扩展性是非常可行的。很容易为集群增加马力来处理负载。集群服务器可立即处理故障并管理故障转移部分,以使您的系统几乎始终可用。如果一台服务器宕机,它会将用户的请求重定向到另一个节点并执行请求的操作。今天,在竞争性营销的世界中,组织的关键是让他们的系统启动并运行。任何故障或停机都会直接影响业务和收入;高可用性是一个不容忽视的因素。

下图向我们展示了我们可能遇到的一些常见性能问题:

读书笔记《hands-on-high-performance-with-spring-5》应用程序性能优化

现在,让我们进入性能调整生命周期的各个阶段。

Performance tuning life cycle

速度是每个企业的核心。在这个高度互联的现代世界中,让大多数人着迷的是速度。无论是最快的汽车、最快的计算机处理器,还是最快的网站。网站性能已成为每个企业的重中之重。用户的期望比以往任何时候都高。如果您的网站没有立即响应,那么您的用户很有可能会转向您的竞争对手。

沃尔玛的一项研究 (https://www.slideshare.net/devonauerswald/walmart-pagespeedslide) 发现,页面性能每改进 1 秒,转化次数就会增加 2%。

Akamai 的一项研究(https://www.akamai.com/us/en/about /news/) 发现:

  • 47% of people expect a web page to load in two seconds or fewer
  • 40% will abandon a web page if it takes more than three seconds to load
  • 52% of online shoppers say quick page loads are important for their loyalty to a site

2007 年,亚马逊报告称,亚马逊的加载时间每增加 100 毫秒 (https://www.amazon.com /),他们的销售额下降了 1%。

借助下图,我们可以轻松了解性能调优生命周期的不同阶段:

读书笔记《hands-on-high-performance-with-spring-5》应用程序性能优化

在大多数情况下,可以通过在正确的时间查看以下工件来避免性能问题:

  • Architecture
  • Design
  • Code
  • Engage expert consultants to perform application reviews at the right time
  • Engage any time before the development phase is completed
  • It is strongly recommended to identify performance optimization issues beforehand, which can start before the completion of the architecture phase
  • It's always better to prevent performance issues before making applications available to users
  • Conduct various reviews and tests to avoid performance issues in production
  • The performance tuning life cycle can also be done after going to production or when facing performance issues in the production environment

为了调整 Spring 应用程序的性能,以下部分中描述的策略非常方便。

Connection pooling

连接池是一种帮助应用程序执行的策略,其中与数据库的 N 个连接在池中打开和监督。应用程序只是请求一个连接,使用它,然后将它放回池中。在应用程序请求连接时,准备好的连接保持可访问性,以便作为池的一部分使用。池处理连接生命周期的程度足以使开发人员真的不必为连接而坐下来并通过陈旧的连接进行转换。

Hibernate 使用它的魔力来识别要使用的连接池提供程序——基于您配置的属性。

以下是 c3p0 连接池的属性配置:

<property name="hibernate.c3p0.min_size">5</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.timeout">300</property>
<property name="hibernate.c3p0.max_statements">50</property>
<property name="hibernate.c3p0.idle_test_period">3000</property>

以下是 Apache Commons DBCP 的示例连接池属性配置:

<property name="hibernate.dbcp.initialSize">8</property>
<property name="hibernate.dbcp.maxActive">20</property>
<property name="hibernate.dbcp.maxIdle">20</property>
<property name="hibernate.dbcp.minIdle">0</property>

当使用任何一种连接池机制时,我们必须手动将 JAR 依赖项放置在服务器类路径中,或者使用 Maven 等依赖项管理工具。

也可以使用 hibernate.connection.provider_class 属性显式指定连接提供程序,但这不是强制性的。

如果我们不使用 Hibernate 配置连接池,则使用默认值。当我们启动应用程序时,它在日志或控制台输出中可见:

org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure

Hibernate 的默认连接池对于开发环境来说是一个不错的选择,但是在生产环境中,建议根据需求和用例来配置连接池。

如果您使用的是应用服务器,您可能希望使用内置池(通常,使用 Java 命名和目录接口 (JNDI) 获得连接) .

要使用 JNDI 配置将服务器的内置池与 Hibernate 会话一起使用,我们需要在 Hibernate 配置文件中设置以下属性:

hibernate.connection.datasource=java:/comp/env/jdbc/AB_DB

假设 AB_DB 是 Tomcat JDBC 连接池 DataSource 的 JNDI 名称。

如果您不能或不希望使用应用程序服务器的内置连接池,Hibernate 支持其他几个连接池,例如:

  • c3p0
  • Proxool

在 Apache DBCP 之后,第二受欢迎的连接池实现是 c3p0,它很容易与 Hibernate 集成,据说可以提供良好的性能。

Hibernate

连接池机制确保应用程序在急需数据库连接时不会耗尽数据库连接。 Hibernate 是用于基于 Java 的应用程序的最佳 ORM 框架之一。使用时,必须针对性能优化进行调整。

Transaction

Hibernate 只在需要时进行肮脏的检查,以记住执行成本。当特定物质具有包含大量段的相关表时,成本会增加。为了尝试限制肮脏的检查成本,我们最好通过确定要仔细阅读的交换来帮助 Spring,这可以更好地增强执行,消除对任何肮脏检查的要求。

下面是一个使用@Transactional注解的例子,它表示该方法在Hibernate事务中运行:

@Transactional(readOnly=true)
public void someBusinessMethod() {
    ....
}

Periodical clearing of Hibernate sessions

在数据库中包含/调整信息时,Hibernate 维护会话。在会话中,它存储要保留的实例的形式。 如果这些实例或记录在会话关闭之前被更改或修改,则称为肮脏检查。 尽管如此,我们可以防止 Hibernate 在其会话中保留元素的时间超过实际需要的时间。因此,一旦完成要求,我们就不必再将实例保留在会话中。对于这种情况,我们可以安全地刷新和清除 EntityManager 以调整数据库中元素的条件并将实例从会话中驱逐出去。这将使应用程序远离内存需求,并且毫无疑问会影响更高方面的执行。

以下是可用于flush()clear() Hibernate 会话的一段代码:

entityManager.flush();
entityManager.clear();

Lazy initialization

如果您使用的是 Hibernate,则应注意充分使用 IN 语句。它仅在需要时才延迟加载记录。当此类自定义记录被低效加载到内存中时,每条记录将独立堆叠并单独使用。因此,如果内存中加​​载的实例过多,则会连续执行相同数量的查询,这可能会导致重大执行命中。

Constructor-based HQLs

在典型情况下,当应用程序使用 Hibernate 时,我们不会尝试恢复具有每个属性的整个物质,尽管事实上我们不需要为特定用例处理每个属性。一个单独的物质可能有 30 个属性,而我们可能只需要在我们的功能中设置几个属性或向客户显示。在这种情况下,通过对数据库的查询来检索大量记录。考虑到应用程序中未使用的字段,它会增加大量负载,最终将导致巨大的执行或性能损失。

为了管理这一点,HQL/JPA 为我们提供了一个 select new 构造函数调用,它经常用于详细查询,这也使设计人员能够选择收集的值。

Entity and query caching

如果每次都针对特定元素进行类似的查询,并且表格信息不会因特定可用性而更改,我们可以使用 Hibernate 存储问题和元素。

在连接查询存储的情况下,此时,不会将生成的 SQL 连接发送到数据库以供执行。如果查询存储或一级缓存无法根据标识符找到元素,则使用存储的元素标识符到达 Hibernate 的二级存储,其中保留了比较真实元素。 这会极大地影响反应时间。当我们这样做时,我们同样担心储备何时会活跃起来。我们可以通过一些基本设置轻松地做到这一点

Native queries

尽管当地的调查出了差错,但在执行方面他们是最快的。当 HQL 更改不妨碍增强应用程序的执行时,本地问题基本上可以将执行提高约 40%。

Primary key generation

在将 Hibernate 注释指示到物质类或编写 .hbm 文档时,我们应该避免使用自动密钥年龄方法,这会引发大量的继任调用。

以下是定义密钥生成策略的示例代码:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "your_key_generator")
private Long id;

通过这个简单的更改,在插入密集型应用程序中可以注意到 10-20% 范围内的改进,基本上没有代码更改。

Database

一旦执行了 Hibernate 性能优化生命周期,下一步就是在数据库级别执行优化生命周期。以下部分定义了数据库组件的性能改进技术。

Indexing

如果查询中包含的表具有大量列,则列表将成为必不可少的因素。此外,它会影响复杂的数据库查询何时被应用程序终止。获得所需索引建议的最理想方法是检查查询执行计划。在分析用于索引的 SQL 查询时,我们必须分别预测每一个真正的查询。

在使用索引时,必须注意:

  • Indexes might slow down inserts and updates, so apply them carefully on columns that are frequently updated
  • Indexes are meant to speed up search operations that use WHERE and ORDER BY clauses in the query

Views

数据库视图是我们在被较长执行时间问题高度包围时探索或思考的另一个过程。在 SQL Server 2000 之前,视图只是为了适应而不是速度。后来的 SQL Server 形式包括一个不常见的组件,称为记录视图,据说可以极大地扩展执行,但是,必须使用规则排列来制作有序视图。

Spring Security

Spring Security 是任何应用程序最重要的方面之一,尤其是那些在互联网上运行的应用程序。虽然 Spring Security 为应用程序提供了一个安全的外观并防止应用程序受到不必要的访问,但如果管理不当,它会增加很多开销。我们将在接下来的部分中关注 Spring Security 最佳实践。

Authentication cache

Spring Security 执行是偶尔出现的担忧之一,当需求处理时间被认为很长,因此不可接受时。在某些情况下,您会看到真正的需求处理大约需要 120 毫秒,而 Spring Security 验证/验证需要另外 500-600 毫秒。

LDAP custom authorities

这不是您需要考虑的方法,但它确实为您提供了另一种选择来增强 Spring Security 实现的执行。

在这种方法中,我们使用自己的自定义用法设置用户权限,而不是从 LDAP 确认。这样做有几个很好的理由,应用程序的执行就是其中之一。

Native LDAP

Spring Security 为我们提供了最标准、最可靠的 LDAP 验证用法。使用 Center Spring LDAP,该方法变得有些糟糕,但显示出简化改进的迹象。与 Spring Security 相比,最后一种方法(使用 Center Spring LDAP)从根本上增强了应用程序的执行。这不是首选方法,但我们仍然可以将其视为开发选项之一。

Multithreading

现在的每个应用程序都是多线程的,这意味着它能够同时执行多个操作。

对于每一个可以想象的流线型,对您的应用程序的单次点击可能看起来很充实。尽管如此,对您的应用程序同时命中的堆测试开始阻碍您的应用程序的执行。在这种高度同步的情况下,您可能需要调整 Tomcat 服务器上的线程默认值。如果并发性很高,HTTP 会要求将其暂停,直到线程结束处理它为止。在更离谱的情况下,滞留线会上升,并且招标会超时。

默认的服务器线程使用可以通过在您的业务原理中使用代理结构来补充,以另外从单独的字符串执行流中的策略内部进行同时的非并发调用。

Performance tuning patterns and anti-patterns

性能调优是框架执行的变化。通常在 PC 框架中,这种行为的灵感被称为性能问题,它可以是真实的,也可以是假设的。大多数框架将对扩展的负载做出反应,并在一定程度上降低执行。框架确认更高负载的能力称为多功能性,而改变框架以处理更高负载与执行调优同义。

性能调优涉及以下提到的步骤:

  1. The issue should be surveyed and checked against expected numeric counts for satisfaction.
  2. Measure the execution of the framework before alteration.
  3. Distinguish the piece of the framework that is basic for enhancing the execution. This is known as the bottleneck.
  4. Alter that piece of the framework to evacuate the bottleneck.
  5. Measure the execution of the framework after alteration.
  6. In the event that the adjustment improves the execution, receive it. In the event that the change aggravates the execution, set it back the way it was.

Anti-patterns

既然有模式,软件开发中也有反模式。模式有助于确保应用程序在性能、可伸缩性和优化处理方面的改进。另一方面,代码中反模式的存在表明应用程序执行中存在挑战。反模式以与模式相似的程度影响应用程序,但以负面的方式。性能反模式主要会降低应用程序的性能。我们正在讨论反模式,因为除了遵循模式和最佳实践之外,我们还必须确保我们不遵循或使用反模式。

Architectural anti-patterns

架构中有许多类型的性能反模式。多层反模式描述了一种架构,它试图通过尽可能多的独立逻辑应用层来实现高度抽象。作为开发人员,这样的架构很快就会被识别,因为映射和转换数据所花费的大部分时间都丢失了,而且从接口到数据库的简单传递很复杂。

这种架构的出现通常是因为应用程序应该尽可能地保持灵活性,例如,可以轻松快速地交换 GUI,并且可以保持对其他系统和组件的依赖性较低。层的解耦会导致数据映射和交换期间的性能损失——特别是如果层在物理上也是分开的并且数据交换是通过远程技术进行的,例如简单对象访问协议( SOAP) 或 远程方法调用 (RMI)、Internet Inter-ORB 协议 (IIOP )。

许多映射和转换操作也可能导致更高的垃圾收集活动,称为循环对象问题。作为这种反模式的解决方案,应该仔细检查架构驱动程序,以阐明需要什么样的灵活性和解耦。新的框架方法,例如 JBoss Seam,已经解决了这个问题,并尽量避免映射数据。

另一种架构反模式是所谓的会话缓存。这样做时,应用程序的 Web 会话被误用作大型数据缓存,这严重限制了应用程序的可扩展性。在调整作业中,会话大小通常被测量为远大于 1 MB——在大多数情况下,没有团队成员知道会话的确切内容。大型会话导致 Java 堆非常繁忙,并且只有少数并行用户是可能的。尤其是在使用会话复制集群应用程序时,根据所使用的技术,由于序列化和数据传输造成的性能损失非常高。一些项目有助于获得新硬件和更多内存,但从长远来看,这是一个非常昂贵且有风险的解决方案。

会话缓存的出现是因为应用程序的架构没有明确定义哪些数据是会话相关的,哪些是持久的,即可以随时恢复。在开发过程中,所有数据都快速存储在会话中,因为这是一种非常方便的解决方案——通常这些数据不再从会话中删除。要解决此问题,您应该首先使用生产堆转储对会话进行内存分析,并清理会话中不依赖于会话的数据。如果获取数据的过程对性能至关重要,例如数据库访问,缓存可以对性能产生积极影响。最佳情况下,缓存对框架内的开发人员是透明的。例如,Hibernate 提供了一级和二级缓存来优化对数据的访问,但要小心;这些框架的配置和调优应该由专家来完成,否则你很快就会得到一个新的性能反模式。

Implementing anti-patterns

有许多可用的 Java 性能反模式和调优技巧,但这些技术反模式的问题是它们严重依赖于 Java 版本和制造商,尤其是用例。一个非常常见的反模式是被低估的前端。对于 Web 应用程序,前端通常是性能的致命弱点。 HTML 和 JavaScript 开发对于真正的应用程序开发人员来说通常是一件令人讨厌的事情,因此通常没有针对性能进行优化。即使 DSL 的使用越来越多,连接通常仍然是一个瓶颈,特别是如果它是通过通用移动电信系统 (UMTS) 或通用的移动连接分组无线电服务 (GPRS)。在 Web 2.0 炒作的推动下,Web 应用程序变得越来越复杂,并且越来越接近桌面应用程序。

这种舒适性导致通过许多服务器往返和大页面延长等待时间和更高的服务器和网络负载。有一系列优化基于 Web 的界面的解决方案。使用 GZip 压缩 HTML 页面可显着减少传输的数据量,并且自 HTTP 1.1 以来已得到所有浏览器的支持。 Web 服务器(例如 Apache)具有模块 (mod_gzip) 可以在不更改应用程序的情况下执行压缩。但是,通过始终使用 CSS 并将 CSS 和 JavaScript 源代码交换到您自己的文件中,也可以在 HTML 中快速减小页面大小,以便浏览器更好地缓存它们。此外,正确使用 AJAX 可以显着提高性能,因为可以节省网页的完全重新加载;例如,仅重传列表的内容。

但即使在分析中,通过使页面内容适应用户的要求,可以显着提高表面的性能。例如,如果只有那些需要 80% 时间的字段出现在页面上,则平均传输率可以显着降低;删除的字段被卸载到单独的页面。例如,在许多 Web 应用程序中,有超过 30 个输入字段的表单。在 90% 的情况下,当用户填写这些表单时,他们只填写了两个字段的值,但我们在列表页面或报告中显示了所有这 30 个字段,包括选择框的所有列表。另一种常见的反模式是幻像日志记录,几乎可以在所有项目中找到。虚拟日志记录生成的日志消息实际上不必在活动日志级别中创建。以下代码是该问题的一个示例:

logger.debug ("one log message" + param_1 + "text" + param_2);

尽管该消息不会记录在 INFO 级别,但该字符串已组装。根据调试和跟踪消息的数量和复杂性,这可能会导致巨大的性能损失,尤其是在对象具有被覆盖且成本高昂的 toString() 方法时。解决方案很简单:

if (logger.isDebugEnabled ()) {
  logger.debug ("One log message" + param_1 + "Text" + param_2);
}

在这种情况下,首先查询日志级别,并且只有在 DEBUG 日志级别处于活动状态时才会生成日志消息。为了避免在开发过程中出现性能瓶颈,尤其应该正确理解所使用的框架。大多数商业和开源解决方案都有足够的性能文档,应定期咨询专家以实施解决方案。即使分析发现了框架内的瓶颈,也并不意味着问题出在框架内。在大多数情况下,问题在于误用或配置。

The iterative performance-tuning process

迭代性能调整过程是一组有助于显着提高应用程序性能的指导方针。这些指南可以在迭代中应用,直到获得所需的输出。这些指南也可以应用于各种 Web 应用程序,无论用于构建应用程序的技术如何。

任何应用程序的第一个也是最重要的部分是静态内容的呈现。静态内容的交付是最常见的性能瓶颈之一。静态内容包括图像、徽标、浏览器可执行脚本、级联样式表和主题。由于此内容始终保持不变,因此无需动态提供此内容。相反,应将 Web 服务器(例如 Apache)配置为具有较长的浏览器缓存时间,同时为响应提供静态资源。静态内容交付的这种改进可以显着提高应用程序的整体性能。 Web 服务器还必须配置为压缩静态资源。 Web 加速器可用于缓存 Web 资源。对于内容驱动的公共门户,强烈建议通过网络加速器缓存整个页面。 Varnish 是一个开源的网络加速器工具。

服务器资源监控必须作为迭代性能分析的一部分。原因是随着应用程序的增长,它开始及时在特定实例上占用更多资源。对服务器资源(如 CPU、内存和磁盘 I/O)的更高需求可能导致其超出操作系统限制并容易出现故障。必须建立监控系统以观察资源利用率。资源监控通常包括:

  • Web servers
  • Application servers
  • Processes—Maximum versus actual
  • Threads—Maximum versus actual
  • Memory usage
  • CPU utilization
  • Heap memory as a separate measurement
  • Disk I/O operations
  • Database connections—Maximum versus busy
  • JVM garbage collection
  • Database slow queries
  • Cache
  • Cache hits—Number of times the result is found from the cache
  • Cache misses—Number of times the result is not found from the cache

为了监控资源,可以使用以下工具:

  • jconsole and jvisualvm come bundled with the standard Java Development Kit (JDK). Using these tools, we can monitor JVM, garbage collection execution, cache statistics, threads, CPU usage, memory usage, and database-connection pooling statistics.
  • mpstat and vmstat are available on Linux-based operating systems. Both of these are command-line tools and are used for collecting and reporting processor-related statistics.
  • ifstat and iostat are useful for monitoring system input/output operations.

可能会有一个问题,为什么我们应该在遵循最佳实践的同时进行这个迭代的性能调整过程。迭代性能调优过程的目标如下:

  • Identify bottlenecks in the system's performance at various levels
  • Improve the performance of the portal as per the expectations
  • Find the solution and approach
  • Put the solution workflow in place
  • Understand the performance pain areas of the system
  • Define the performance strategy for the application
  • Identify the performance measurement tool based on the technologies
  • Understand application key user scenarios
  • Document key scenarios
  • Prepare sufficient data to generate considerable distributed load on all flavors in a single execution
  • Customize and combine load testing scripts to prepare a performance test suite that can be used for execution on any single flavor or on all flavors at a time
  • Execute performance scripts with different scenarios and load combinations to identify bottlenecks using response times

在应用程序开发生命周期的所有阶段都遵循迭代性能调整过程。下表显示了正在审查的项目以及输入和输出预期:

Review item Input Output

建筑学

  • High availability
  • Scalability
  • Caching
  • Integration
  • Network
  • Search Engine
  • Database
System architecture diagram Recommendations on best practices

用户界面

  • Frontend code
  • Existing technology selection criteria
  • Review of the code
  • Recommendations for the change

硬件配置

  • Web server details
  • App server details
  • Database details
  • Server type (Virtual or Physical)
  • Number of CPU
  • Hard disk space
  • Memory configuration
Changes recommended in hardware configuration

软件配置

  • Framework configuration
  • Dependent modules/integrations configuration, if any
Recommendations on configuration change

应用服务器配置

  • App server configuration files
Recommendations on configuration change

网络服务器配置

  • Web server configuration files
  • Cache control settings
  • Static resource-handling settings
Recommendations on configuration change

部署架构

  • Deployment diagram
  • Software installation details
  • Configuration details

关于部署架构更改的建议

代码和数据库

  • Code review
  • DB design review
  • Code duplication
  • Modularization of code
  • Any third-party libraries/APIs
  • Coding standard implemented
  • Loops and conditions
  • Data normalization
  • Indexing
  • Long-running queries
  • Relations between tables
  • Code review results
  • Recommendations for improvement

Spring support of JMX

JMX 是 Java 平台中的标准组件。它在 J2SE 5.0 中首次发布。基本上,JMX 是为应用程序和网络管理定义的一组规范。它使开发人员能够将管理属性分配给应用程序中使用的 Java 对象。通过分配管理属性,它使 Java 对象能够与正在使用的网络管理软件一起工作。它为开发人员提供了一种管理应用程序、设备和服务的标准方法。

JMX 具有三层架构。这里定义了三层:

  • The probe or instrumentation layer: This layer contains managed beans. The application resources to be managed are enabled for JMX.
  • The agent or MBeanServer layer: This layer forms the core of the JMX. It works as an intermediary between managed beans and the application.
  • The remote management layer: This layer enables the remote applications to connect to and access MBeanServer using connectors or adapters. The connector provides full access to mBeanServer whereas an adapter adapts the API.

下图展示了 JMX 的架构:

读书笔记《hands-on-high-performance-with-spring-5》应用程序性能优化
Source: https://www.ibm.com/support/knowledgecenter/en/SSAW57_8.5.5/com.ibm.websphere.nd.multiplatform.doc/ae/cxml_javamanagementx.html

Managed beans

托管 bean 是一种 Java bean。它专门用于 JMX 技术,它是使用 依赖注入 (DI) 技术创建的。在 JMX 中,资源表示为 托管 bean (MBean)。这些托管 bean 向核心托管 bean 服务器注册。因此,可以将托管 bean 可视化为 Java 服务、组件或设备的包装器。由于所有托管组件都向 MBeans 服务器注册,因此它用于管理所有托管 bean。托管 bean 服务器允许服务器组件连接并查找托管 bean。典型的 JMX 代理由托管 bean 服务器和与托管 bean 交互所需的服务组成。

JMX 规范描述了标准连接器。这些连接器也称为 JMX 连接器。 JMX 连接器允许我们从远程管理应用程序访问 JMX 代理。连接器可以使用不同的协议来使用相同的管理接口。

以下是应该使用 JMX 的原因:

  • It provides a way to manage applications on different devices
  • It provides a standard way to manage Java applications and networks
  • It can be used to manage JVM
  • It provides a scalable and dynamic management interface

有了对 JMX 的基本了解,让我们继续检查 Spring 是如何支持它的。 Spring 的 JMX 支持使我们能够非常轻松地将 Spring 应用程序转换为 JMX 架构。

以下是 Spring 的 JMX 支持提供的特性:

  • Automatic registration of a Spring bean as a managed bean
  • A flexible structure for controlling the management interface for Spring beans
  • A declarative approach for managed beans over remote connectors
  • Proxying of local and remote managed bean resources

这些特性在不与 Spring 或 JMX 的类或接口耦合的情况下工作。 Spring JMX 支持有一个名为 MBeanExporter 的类。此类负责收集 Spring bean 并将它们注册到托管 bean 服务器。

下面是一个 Spring bean 的示例:

package com.springhighperformance.jmx;

public class Calculator {
  private String name;
  private int lastCalculation;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getLastCalculation() {
    return lastCalculation;
  }

  public void calculate(int x, int y) {
    lastCalculation = x + y;
  }
}

为了将此 bean 及其属性公开为托管属性和操作,应在配置文件中进行以下配置:

<beans>
  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter " lazy-init="false">
    <property name="beans">
      <map>
        <entry key="bean:name=calculatorBean1" value- ref="calculatorBean"/>
      </map>
    </property>
  </bean>

  <bean id="calculatorBean" class="com.springhighperformance.jmx.Calculator">
    <property name="name" value="Test"/>
    <property name="lastCalculation" value="10"/>
  </bean>
</beans>

从前面的配置中,要查找的一个重要 bean 定义是 exporter bean。导出器 bean 的 beans 映射属性指示 Spring bean 作为 JMX bean 公开给 JMX 管理的 bean 服务器。

使用上述配置,假定托管 bean 服务器必须运行在 Spring 应用程序可访问的环境中。如果托管 bean 服务器或 MBeanServer 正在运行,Spring 将尝试找到它并注册所有 bean。当应用程序在具有捆绑 MBeanServer 的 Tomcat 或 IBM WebSphere 中运行时,此默认行为很有用。

在其他情况下,我们必须创建一个 MBeanServer 实例,如下所示:

<beans>
  <bean id="mbeanServer" class="org.springframework.jmx.support. MBeanServerFactoryBean"/>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=calculatorBean1" value- ref="calculatorBean"/>
      </map>
    </property>
    <property name="server" ref="mbeanServer"/>
  </bean>

  <bean id="calculatorBean" class="com.springhighperformance.jmx.Calculator">
    <property name="name" value="Test"/>
    <property name="lastCalculation" value="10"/>
  </bean>
</beans>

我们必须在 MBeanExporter bean 上指定服务器属性,以将其与已创建的 MBeanServer 相关联。

随着 JDK 5.0 中注解的出现,Spring 启用了设置注解以将 Spring bean 注册为 JMX bean 的规定。

以下是使用 @ManagedResource 注释定义的示例 Calculator bean:

package com.springhighperformance.jmx;

import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.jmx.export.annotation.ManagedResource;

  @ManagedResource(objectName = "Examples:type=JMX,name=Calculator",
    description = "A calculator to demonstrate JMX in the 
    SpringFramework")
  public class Calculator {
  private String name;
  private int lastCalculation;

  @ManagedAttribute(description = "Calculator name")
  public String getName() {
    return name;
  }

  @ManagedAttribute(description = "Calculator name")
  public void setName(String name) {
    this.name = name;
  }

  @ManagedAttribute(description = "The last calculation")
  public int getLastCalculation() {
    return lastCalculation;
  }

  @ManagedOperation(description = "Calculate two numbers")
  @ManagedOperationParameters({
      @ManagedOperationParameter(name = "x",
          description = "The first number"),
      @ManagedOperationParameter(name = "y",
          description = "The second number") })
  public void calculate(int x, int y) {
    lastCalculation = x + y;
  }
}

@ManagedAttribute@ManagedOperation 注释用于公开 bean 属性和方法来管理 bean 服务器。

以下是实例化托管 bean 的客户端代码,可以通过 JConsole 或 VisualVM 等工具进行监控:

package com.springhighperformance.jmx;
import java.util.Random;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;

@Configuration
@EnableMBeanExport
public class JmxSpringMain {
  private static final Random rand = new Random();
  
    @Bean
    public Resource jmxResource() {
        return new Resource();
    }

    @Bean
    public Calculator calculator() {
        return new Calculator();
    }

    public static void main(String[] args) throws InterruptedException {
        ApplicationContext context = new 
        AnnotationConfigApplicationContext(JmxSpringMain.class);
        do {
          Calculator cal = context.getBean(Calculator.class);
          cal.calculate(rand.nextInt(), rand.nextInt());
          Thread.sleep(Long.MAX_VALUE);
        } while(true);
    }
}

一旦暴露为托管 bean,就可以使用 JConsole 或 VisualVM 等工具监控这些资源的各种参数,例如对象数量、对象占用的内存以及对象占用的堆内存空间。

以下是 Java VisualVM 的屏幕截图,突出显示了 Calculator 作为托管 bean:

读书笔记《hands-on-high-performance-with-spring-5》应用程序性能优化

Summary

这是本书最重要的章节之一。它只关注性能测量和增强策略。本章类似于现实生活中的健康检查场景。如果一个人身体不适,第一步是识别症状,以便识别和治愈疾病。同样,本章从识别性能下降的症状开始,然后进入性能调优生命周期。描述了性能调整模式和反模式,它们类似于要遵循的最佳实践。随后是迭代性能调优过程和 Spring 框架中的 JMX 支持。我们看到了一个将 Spring bean 转换为 JMX 管理的 bean 的示例。

下一章的重点是微调 JVM。这不会是特定于 Spring 应用程序的调整,但适用于在 JVM 上运行的任何应用程序。本章将深入探讨开发人员不太了解的 JVM 内部细节。让我们准备好深入研究 JVM。