系统优化专题1
一、概述
为什么要做性能调优?
性能调优可以使系统稳定,用户体验更佳,甚至在比较大的系统中,还可以帮公司节约资源。
什么时候开始做性能调优?
项目开始阶段,没有必要过早的介入性能优化,只需要在编码的时候保证其优秀,高效,以及良好的程序设计。项目完成以后,我们就可以进行系统测试了,我们可以将性能指标,作为性能调校的标准。除了通过观察指标确定系统性能的好坏,还需要在更新迭代中,充分保障系统的稳定性。
这里延申一个方法,将迭代之前版本的系统性能指标作为参考标准,通过自动化性能测试,校验迭代发版之后的系统性能是否出现异常,这就不仅需要比较观察指标,还要比较系统资源的CPU占用率、内存使用率、磁盘I/O、网络I/O等几项间接指标的变化。
性能调优的标准?
- 响应时间:响应时间是衡量系统性能的重要指标之一,响应时间越短,性能越好,一般一个接口的响应时间在毫秒级。
- 数据库的响应时间:
- 服务端响应时间:包括nginx分发及服务端程序执行消耗的时间。
- 网络响应时间:网络传输时,网络硬件对请求解析等操作消耗的时间。
- 客户端响应时间:对于普通的web、app客户端来说,这段时间是可以忽略不计的,但如果客户端嵌入了大量的逻辑处理消耗的时间就有可能成为系统瓶颈。
- 吞吐量:测试中我们往往会比较注重接口的TPS,因为TPS体现了接口的性能,TPS越大,性能越好。在系统中我们也可以把吞吐量分为磁盘吞吐量与网络吞吐量两种。
- 磁盘吞吐量的指标:IOPS每秒钟的输入输出量,指单位时间内系统能处理的I/O请求数量,I/O通常是读写请求,关注随机读写性能,文件存储类服务磁盘吞吐量是关键指标。
- 数据吞吐量:是单位时间内能成功传输的数据量,如视频点播等,数据吞吐量是关键指标。
- 网络吞吐量:指网络没有帧丢失时,设备能接受的最大数据速率。网络吞吐量不仅和带宽有关,还与CPU的处理能力、网卡、防火墙、外部接口和I/O等紧密关联。吞吐量大小主要由网卡的处理能力和程序算法和带宽大小决定。
- 计算机资源分配使用率:
通常由CPU占用率,内存使用率,磁盘I/O,网络I/O来表示资源使用率。其中任何一个资源分配不合理,对整个系统性能的影响都是极大的。 - 负载承受能力:当系统压力上升时,可以观看系统响应时间的上升曲线是否平缓。这项指标将直观的反馈系统所能承受的压力极限。
系统的性能指标
- CPU:CPU使用率
- 内存:内存占用
- 磁盘I/O:磁盘读写速度
- 网络:带宽
- 异常:在java应用中,抛出异常需要构建堆栈,捕获异常需要消耗系统性能,如果高并发的情况下引发系统异常,持续进行异常处理,系统的性能会明显受到影响。
- 数据库:大部分的系统会使用数据库,而数据库的操作往往涉及到磁盘I/O。大量的读写操作会导致I/O瓶颈,导致数据库操作的延迟,对于大量读写数据库的系统,数据库的性能优化是系统优化的核心。
- 锁竞争:在并发编程中,多线程共享读写一个资源,为了保证数据的原子性,我们就会用到锁。锁会带来上下文切换。JDK1.6以后,Java对JVM内部锁已经做了多次优化。想要合理的利用优化锁,需要了解更多的操作系统知识,java多线程编程基础。
注:
- 系统负载代表单位时间内正在运行获等待的进程或线程数,代表了系统的繁忙程度。对于CPU密集型的程序来说,系统的负载未必会高,但是CPU的利用率会高。对于I/O密集型的程序来说可能CPU利用率不高,但是系统负载会很高,这是由于I/O经常会引起阻塞。
- TPS:代表单位时间内事务的处理数量,一个事务可以包含多次请求。
- QPS:单位时间内处理的请求数量。当一次用户操作只包含一个请求接口时,TPS和QPS没有区别,当用户的一次操作包含了多个服务请求时,这时候TPS作为性能指标就更具代表性。
- 当Java程序捕获异常时,会调用父类Throwable的fillinStackTrace方法生成栈追踪信息,为运行时栈做一份快照,平时的业务异常应避免生成栈追踪信息,在异常中用字符串描述业务异常信息即可,具体操作为,实现自定义异常,继承RuntimeException,将writableStackTrace设置为false即可。
- 对于报表类实时导出大量业务数据的需求,应避免在朱库中进行这种操作,写入和查询都在一个数据库进行会导致数据库性能瓶颈,严重的会导致数据库死锁,可以将数据库读写分离,如果公司有大数据中心,也可以考虑将数据实时同步到大数据中心,通过实时的流计算处理生成不同需求的业务数据。
二、如何制定性能调优策略
基本步骤: 测试-分析-调优
2.1性能测试攻略:
微基准性能测试
微基准性能测试可以精准定位到某个模块或者某个方法的性能问题,特别适合做一个功能模块或者一个方法在不同实现方法下的性能对比。例如,对比一个方法使用同步实现和非同步实现的性能。
宏基准测试
宏基准测试是一个综合测试,需要考虑到环境、测试场景和测试目标。测试环境需要模拟线上的真实环境。然后看测试场景,我们需要考虑在测试某个接口时,是否有其他业务接口同实在平行运行,造成干扰。
最后看测试目标,如果不达标就需要进行优化,如果达标了,就需要加大测试力度,对测试接口进行探底。
在测试时我们还有一些问题需要注意,
- 热身问题: 在做性能测试时,系统会运行的越来越快,后面的访问速度会比第一次访问的速度快几倍。在Java语言中,虚拟机为了解决内存提高执行效率,当某段代码被频繁执行时,虚拟机会通过即时编译器(JIT Compiler,just-in-time cpmpiler)将这些代码编译成与本地平台相关的机器码,并进行各层次的优化,然后存储在内存中。
- 性能测试结果不稳定: 我们在做性能测试时发现,每次测试处理的数据集都是一样的,但结果却均不相同,对此我们可以将多个测试结果平均,或者设计一个曲线图,只要保证平均值在合理范围内,并且波动不大,那么测试就是通过的。
- 多个JVM情况的影响: 如果一台机器有多个JVM,那么任意一个JVM都拥有系统资源的使用权,所以会造成测试结果与运行结果不符。建议线上环境中尽量避免一台机器部署多个JVM。
2.2合理分析结果,制定调优策略
<p>我们在完成性能测试之后,需要输出一份性能测试报告,帮助我们分析系统性能测试的情况。其中测试结果需要包含接口的平均、最大和最小吞吐量,响应时间,服务器的CPU、内存、I/O、网络IO使用率JVM的GC频率。</p>
<p>通过观察这些调优标准,可以发现性能瓶颈,我们再通过自下而上的方式分析查找问题。首先从操作系统层面,查看系统的各项指标是否存在问题,再查找日志,通过分析日志找到导致瓶颈的原因;还可以从Java应用的JVM层面查看垃圾回收频率和内存分配情况是否存在异常,分析日志,找到导致瓶颈的原因。</p>
<p>如果问题没有出在系统和JVM层面,我们可以查看应用是否出现问题,例如编程问题,读写数据库瓶颈等。</p>
<p>分析查找问题是一个复杂的过程,某个问题可能是几个原因共同导致的,分析问题的时候我们采用自下而上的方式,而解决问题时我们采用自上而下的方式逐级优化。</p>
2.3几种常用的系统优化方案
- 优化代码:优化代码结构使用合适的算法实现等。
- 优化设计:使用设计模式,不仅能精简代码,还可以提高整体性能。
- 优化算法:好的算法可以帮我们提升性能,在不同的场景中,使用合适的算法,可以减低时间复杂度。
- 时间换空间:有时候系统对时间没有很高的要求,反而对存储的要求比较苛刻,这时候我们可以考虑使用时间来换空间。
- 空间换时间:这种方法是使用存储空间来提升访问速度。常见的分库分表就是使用空间换时间的案例。
- 参数调优:除上述优化的方法外,JVM、web容器和操作系统的优化也是非常关键的。根据业务场景,合理地设置JVM的内存和垃圾回收算法可以提升系统性能。例如,通过设置将大对象直接放入老年代,这样可以减少年轻代频繁发生GC,减少CPU占用时间,提升性能。
以上的几种方式都是提高系统性能的手段,但无论我们优化的多好,系统还是会存在承受极限,为了保证系统的稳定性,我们还是需要采用一些兜底策略。
2.4 兜底策略
- 限流,对系统的入口设置最大访问限制,这里可以参考探底的TPS。同时采取熔断措施,友好的返回没有成功的请求。
- 智能化的横向扩容,横向扩容可以保证当访问量超过某个阈值时,系统可以自动新增服务。
- 提前扩容。这种方式通常用于高并发系统,通常是横向扩容无法满足瞬时大并发。现在许多公司使用Kubernetes作为容器管理系统,其可以实现智能横向扩容和提前扩容docker服务。
结语:
任何调优都需要结合场景明确已知问题和性能目标,不要为了调优而调优,以免引入新Bug。