《Java性能调优实战》模块一 · 概述

Posted by 瞿广 on Tuesday, August 6, 2019
Last Modified on Sunday, August 4, 2019

TOC

为什么要做性能调优?一款线上产品如果没有经过性能测试,那它就好比是一颗定时炸弹,你不知道它什么时候会出现问题,你也 不清楚它能承受的极限在哪儿。

好的系统性能调优不仅仅可以提高系统的性能,还能为公司节省资源。这也是我们做性能调优的最直接的目 的。

课程目录

开篇词 (1讲)


开篇词 | 怎样才能做好性能调优?
免费
模块一 · 概述 (2讲)


01 | 如何制定性能调优标准?
02 | 如何制定性能调优策略?

模块二 · Java编程性能调优 (10讲)

03 | 字符串性能优化不容小觑,百M内存轻松存储几十G数据
04 | 慎重使用正则表达式
05 | ArrayList还是LinkedList?使用不当性能差千倍

加餐 | 推荐几款常用的性能测试工具

06 | Stream如何提高遍历集合效率?
07 | 深入浅出HashMap的设计与优化
08 | 网络通信优化之I/O模型:如何解决高并发下I/O瓶颈?
09 | 网络通信优化之序列化:避免使用Java序列化
10 | 网络通信优化之通信协议:如何优化RPC网络通信?
11 | 答疑课堂:深入了解NIO的优化实现原理


模块三 · 多线程性能调优 (10讲)

12 | 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法
13 | 多线程之锁优化(中):深入了解Lock同步锁的优化方法
14 | 多线程之锁优化(下):使用乐观锁优化并行操作
15 | 多线程调优(上):哪些操作导致了上下文切换?
16 | 多线程调优(下):如何优化多线程上下文切换?
17 | 并发容器的使用:识别不同场景下最优容器
18 | 如何设置线程池大小?
19 | 如何用协程来优化多线程业务?

答疑课堂:模块三热点问题解答
加餐 | 什么是数据的强、弱一致性?

模块四 · JVM性能监测及调优 (6讲)

20 | 磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型
21 | 深入JVM即时编译器JIT,优化Java编译
22 | 如何优化垃圾回收机制?
23 | 如何优化JVM内存分配?
24 | 内存持续上升,我该如何排查问题?
25 | 答疑课堂:模块四热点问题解答

模块五 · 设计模式调优 (6讲)

26 | 单例模式:如何创建单一对象优化系统性能?
27 | 原型模式与享元模式:提升系统性能的利器
28 | 如何使用设计模式优化并发编程?
29 | 生产者消费者模式:电商库存设计优化
30 | 装饰器模式:如何优化电商系统中复杂的商品价格策略?
31 | 答疑课堂:模块五思考题集锦

模块六 · 数据库性能调优 (7讲)

32 | MySQL调优之SQL语句:如何写出高性能SQL语句?
33 | MySQL调优之事务:高并发场景下的数据库事务调优
34 | MySQL调优之索引:索引的失效与优化
35 | 记一次线上SQL死锁事故:如何避免死锁?
36 | 什么时候需要分表分库?
37 | 电商系统表设计优化案例分析
38 | 数据库参数设置优化,失之毫厘差之千里

01-如何制定性能调优标准?

什么时候开始介入调优?

解决了为什么要做性能优化的问题,那么新的问题就来了:如果需要对系统做一次全面的性能监测和优化, 我们从什么时候开始介入性能调优呢?是不是越早介入越好? 其实,在项目开发的初期,我们没有必要过于在意性能优化,这样反而会让我们疲于性能优化,不仅不会给

系统性能带来提升,还会影响到开发进度,甚至获得相反的效果,给系统带来新的问题。 我们只需要在代码层面保证有效的编码,比如,减少磁盘 I/O 操作、降低竞争锁的使用以及使用高效的算法 等等。遇到比较复杂的业务,我们可以充分利用设计模式来优化业务代码。例如,设计商品价格的时候,往 往会有很多折扣活动、红包活动,我们可以用装饰模式去设计这个业务。 在系统编码完成之后,我们就可以对系统进行性能测试了。这时候,产品经理一般会提供线上预期数据,我 们在提供的参考平台上进行压测,通过性能分析、统计工具来统计各项性能指标,看是否在预期范围之内。 在项目成功上线后,我们还需要根据线上的实际情况,依照日志监控以及性能统计日志,来观测系统性能问 题,一旦发现问题,就要对日志进行分析并及时修复问题。

有哪些参考因素可以体现系统的性能?

上面我们讲到了在项目研发的各个阶段性能调优是如何介入的,其中多次讲到了性能指标,那么性能指标到 底有哪些呢?

在我们了解性能指标之前,我们先来了解下哪些计算机资源会成为系统的性能瓶颈。

  • CPU:有的应用需要大量计算,他们会长时间、不间断地占用 CPU 资源,导致其他资源无法争夺到 CPU 而 响应缓慢,从而带来系统性能问题。例如,代码递归导致的无限循环,正则表达式引起的回溯,JVM频繁的 FULL GC,以及多线程编程造成的大量上下文切换等,这些都有可能导致 CPU 资源繁忙。
  • 内存:Java 程序一般通过 JVM 对内存进行分配管理,主要是用 JVM 中的堆内存来存储 Java 创建的对象。系统堆内存的读写速度非常快,所以基本不存在读写性能瓶颈。但是由于内存成本要比磁盘高,相比磁盘,内存的存储空间又非常有限。所以当内存空间被占满,对象无法回收时,就会导致内存溢出、内存泄露等问 题。
  • 磁盘I/O:磁盘相比内存来说,存储空间要大很多,但磁盘 I/O 读写的速度要比内存慢,虽然目前引入的 SSD 固态硬盘已经有所优化,但仍然无法与内存的读写速度相提并论。
  • 网络:网络对于系统性能来说,也起着至关重要的作用。如果你购买过云服务,一定经历过,选择网络带宽 大小这一环节。带宽过低的话,对于传输数据比较大,或者是并发量比较大的系统,网络就很容易成为性能 瓶颈。
  • 异常:Java 应用中,抛出异常需要构建异常栈,对异常进行捕获和处理,这个过程非常消耗系统性能。如 果在高并发的情况下引发异常,持续地进行异常处理,那么系统的性能就会明显地受到影响。
  • 数据库:大部分系统都会用到数据库,而数据库的操作往往是涉及到磁盘 I/O 的读写。大量的数据库读写操 作,会导致磁盘 I/O 性能瓶颈,进而导致数据库操作的延迟性。对于有大量数据库读写操作的系统来说,数 据库的性能优化是整个系统的核心。
  • 锁竞争:在并发编程中,我们经常会需要多个线程,共享读写操作同一个资源,这个时候为了保持数据的原 子性(即保证这个共享资源在一个线程写的时候,不被另一个线程修改),我们就会用到锁。锁的使用可能 会带来上下文切换,从而给系统带来性能开销。JDK1.6 之后,Java 为了降低锁竞争带来的上下文切换,对 JVM 内部锁已经做了多次优化,例如,新增了偏向锁、自旋锁、轻量级锁、锁粗化、锁消除等。而如何合理 地使用锁资源,优化锁资源,就需要你了解更多的操作系统知识、Java 多线程编程基础,积累项目经验,并结合实际场景去处理相关问题。

了解了上面这些基本内容,我们可以得到下面几个指标,来衡量一般系统的性能。

响应时间

响应时间是衡量系统性能的重要指标之一,响应时间越短,性能越好,一般一个接口的响应时间是在毫秒 级。在系统中,我们可以把响应时间自下而上细分为以下几种:

  • 数据库响应时间:数据库操作所消耗的时间,往往是整个请求链中最耗时的; 服务端响应时间:服务端包括 Nginx 分发的请求所消耗的时间以及服务端程序执行所消耗的时间;
  • 网络响应时间:这是网络传输时,网络硬件需要对传输的请求进行解析等操作所消耗的时间;
  • 客户端响应时间:对于普通的 Web、App 客户端来说,消耗时间是可以忽略不计的,但如果你的客户端 嵌入了大量的逻辑处理,消耗的时间就有可能变长,从而成为系统的瓶颈。

吞吐量

在测试中,我们往往会比较注重系统接口的 TPS(每秒事务处理量),因为 TPS 体现了接口的性能,TPS 越 大,性能越好。在系统中,我们也可以把吞吐量自下而上地分为两种:磁盘吞吐量和网络吞吐量。 我们先来看磁盘吞吐量,磁盘性能有两个关键衡量指标。

一种是 IOPS(Input/Output Per Second),即每秒的输入输出量(或读写次数),这种是指单位时间内系 统能处理的 I/O 请求数量,I/O 请求通常为读或写数据操作请求,关注的是随机读写性能。适应于随机读写 频繁的应用,如小文件存储(图片)、OLTP 数据库、邮件服务器。

另一种是数据吞吐量,这种是指单位时间内可以成功传输的数据量。对于大量顺序读写频繁的应用,传输大 量连续数据,例如,电视台的视频编辑、视频点播 VOD(Video On Demand),数据吞吐量则是关键衡量 指标。

接下来看网络吞吐量,这个是指网络传输时没有帧丢失的情况下,设备能够接受的最大数据速率。网络吞吐 量不仅仅跟带宽有关系,还跟 CPU 的处理能力、网卡、防火墙、外部接口以****及 I/O 等等紧密关联。而 吞吐量的大小主要由网卡的处理能力、内部程序算法以及带宽大小决定。

计算机资源分配使用率

通常由 CPU 占用率、内存使用率、磁盘 I/O、网络 I/O 来表示资源使用率。这几个参数好比一个木桶,如 果其中任何一块木板出现短板,任何一项分配不合理,对整个系统性能的影响都是毁灭性的。

负载承受能力

当系统压力上升时,你可以观察,系统响应时间的上升曲线是否平缓。这项指标能直观地反馈给你,系统所 能承受的负载压力极限。例如,当你对系统进行压测时,系统的响应时间会随着系统并发数的增加而延长, 直到系统无法处理这么多请求,抛出大量错误时,就到了极限。

总结

通过今天的学习,我们知道性能调优可以使系统稳定,用户体验更佳,甚至在比较大的系统中,还能帮公司 节约资源。 但是在项目的开始阶段,我们没有必要过早地介入性能优化,只需在编码的时候保证其优秀、高效,以及良 好的程序设计。 在完成项目后,我们就可以进行系统测试了,我们可以将以下性能指标,作为性能调优的标准,响应时间、 吞吐量、计算机资源分配使用率、负载承受能力。

回顾我自己的项目经验,有电商系统、支付系统以及游戏充值计费系统,用户级都是千万级别,且要承受各 种大型抢购活动,所以我对系统的性能要求非常苛刻。除了通过观察以上指标来确定系统性能的好坏,还需 要在更新迭代中,充分保障系统的稳定性。

这里,给你延伸一个方法,就是将迭代之前版本的系统性能指标作为参考标准,通过自动化性能测试,校验 迭代发版之后的系统性能是否出现异常,这里就不仅仅是比较吞吐量、响应时间、负载能力等直接指标了, 还需要比较系统资源的 CPU 占用率、内存使用率、磁盘 I/O、网络 I/O 等几项间接指标的变化。

02-如何制定性能调优策略?

“测试-分析-调优”三步走。

性能测试攻略

  1. 微基准性能测试

微基准性能测试可以精准定位到某个模块或者某个方法的性能问题,特别适合做一个功能模块或者一个方法 在不同实现方式下的性能对比。例如,对比一个方法使用同步实现和非同步实现的性能。

  1. 宏基准性能测试

宏基准性能测试是一个综合测试,需要考虑到测试环境、测试场景和测试目标。

首先看测试环境,我们需要模拟线上的真实环境。

然后看测试场景。我们需要确定在测试某个接口时,是否有其他业务接口同时也在平行运行,造成干扰。如 果有,请重视,因为你一旦忽视了这种干扰,测试结果就会出现偏差。

最后看测试目标。我们的性能测试是要有目标的,这里可以通过吞吐量以及响应时间来衡量系统是否达标。 不达标,就进行优化;达标,就继续加大测试的并发数,探底接口的 TPS(最大每秒事务处理量),这样 做,可以深入了解到接口的性能。除了测试接口的吞吐量和响应时间以外,我们还需要循环测试可能导致性 能问题的接口,观察各个服务器的 CPU、内存以及 I/O 使用率的变化。

以上就是两种测试方法的详解。其中值得注意的是,性能测试存在干扰因子,会使测试结果不准确。所以, 我们在做性能测试时,还要注意一些问题。

  1. 热身问题 当我们做性能测试时,我们的系统会运行得越来越快,后面的访问速度要比我们第一次访问的速度快上几 倍。这是怎么回事呢?

在 Java 编程语言和环境中,.java 文件编译成为 .class 文件后,机器还是无法直接运行 .class 文件中的字节 码,需要通过解释器将字节码转换成本地机器码才能运行。为了节约内存和执行效率,代码最初被执行时, 解释器会率先解释执行这段代码。

随着代码被执行的次数增多,当虚拟机发现某个方法或代码块运行得特别频繁时,就会把这些代码认定为热点代码(Hot Spot Code)。为了提高热点代码的执行效率,在运行时,虚拟机将会通过即时编译器(JIT compiler,just-in-time compiler)把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,然 后存储在内存中,之后每次运行代码时,直接从内存中获取即可。

所以在刚开始运行的阶段,虚拟机会花费很长的时间来全面优化代码,后面就能以最高性能执行了。 这就是热身过程,如果在进行性能测试时,热身时间过长,就会导致第一次访问速度过慢,你就可以考虑先 优化,再进行测试。

  1. 性能测试结果不稳定

我们在做性能测试时发现,每次测试处理的数据集都是一样的,但测试结果却有差异。这是因为测试时,伴 随着很多不稳定因素,比如机器其他进程的影响、网络波动以及每个阶段 JVM 垃圾回收的不同等等。 我们可以通过多次测试,将测试结果求平均,或者统计一个曲线图,只要保证我们的平均值是在合理范围之 内,而且波动不是很大,这种情况下,性能测试就是通过的。

  1. 多JVM情况下的影响 如果我们的服务器有多个 Java 应用服务,部署在不同的 Tomcat 下,这就意味着我们的服务器会有多个 JVM。任意一个 JVM 都拥有整个系统的资源使用权。如果一台机器上只部署单独的一个 JVM,在做性能测试 时,测试结果很好,或者你调优的效果很好,但在一台机器多个 JVM 的情况下就不一定了。所以我们应该 尽量避免线上环境中一台机器部署多个 JVM 的情况。