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-如何制定性能调优策略?
“测试-分析-调优”三步走。
性能测试攻略
- 微基准性能测试
微基准性能测试可以精准定位到某个模块或者某个方法的性能问题,特别适合做一个功能模块或者一个方法 在不同实现方式下的性能对比。例如,对比一个方法使用同步实现和非同步实现的性能。
- 宏基准性能测试
宏基准性能测试是一个综合测试,需要考虑到测试环境、测试场景和测试目标。
首先看测试环境,我们需要模拟线上的真实环境。
然后看测试场景。我们需要确定在测试某个接口时,是否有其他业务接口同时也在平行运行,造成干扰。如 果有,请重视,因为你一旦忽视了这种干扰,测试结果就会出现偏差。
最后看测试目标。我们的性能测试是要有目标的,这里可以通过吞吐量以及响应时间来衡量系统是否达标。 不达标,就进行优化;达标,就继续加大测试的并发数,探底接口的 TPS(最大每秒事务处理量),这样 做,可以深入了解到接口的性能。除了测试接口的吞吐量和响应时间以外,我们还需要循环测试可能导致性 能问题的接口,观察各个服务器的 CPU、内存以及 I/O 使用率的变化。
以上就是两种测试方法的详解。其中值得注意的是,性能测试存在干扰因子,会使测试结果不准确。所以, 我们在做性能测试时,还要注意一些问题。
- 热身问题 当我们做性能测试时,我们的系统会运行得越来越快,后面的访问速度要比我们第一次访问的速度快上几 倍。这是怎么回事呢?
在 Java 编程语言和环境中,.java 文件编译成为 .class 文件后,机器还是无法直接运行 .class 文件中的字节 码,需要通过解释器将字节码转换成本地机器码才能运行。为了节约内存和执行效率,代码最初被执行时, 解释器会率先解释执行这段代码。
随着代码被执行的次数增多,当虚拟机发现某个方法或代码块运行得特别频繁时,就会把这些代码认定为热点代码(Hot Spot Code)。为了提高热点代码的执行效率,在运行时,虚拟机将会通过即时编译器(JIT compiler,just-in-time compiler)把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,然 后存储在内存中,之后每次运行代码时,直接从内存中获取即可。
所以在刚开始运行的阶段,虚拟机会花费很长的时间来全面优化代码,后面就能以最高性能执行了。 这就是热身过程,如果在进行性能测试时,热身时间过长,就会导致第一次访问速度过慢,你就可以考虑先 优化,再进行测试。
- 性能测试结果不稳定
我们在做性能测试时发现,每次测试处理的数据集都是一样的,但测试结果却有差异。这是因为测试时,伴 随着很多不稳定因素,比如机器其他进程的影响、网络波动以及每个阶段 JVM 垃圾回收的不同等等。 我们可以通过多次测试,将测试结果求平均,或者统计一个曲线图,只要保证我们的平均值是在合理范围之 内,而且波动不是很大,这种情况下,性能测试就是通过的。
- 多JVM情况下的影响 如果我们的服务器有多个 Java 应用服务,部署在不同的 Tomcat 下,这就意味着我们的服务器会有多个 JVM。任意一个 JVM 都拥有整个系统的资源使用权。如果一台机器上只部署单独的一个 JVM,在做性能测试 时,测试结果很好,或者你调优的效果很好,但在一台机器多个 JVM 的情况下就不一定了。所以我们应该 尽量避免线上环境中一台机器部署多个 JVM 的情况。