资深架构师手把手教你性能优化
图片来源:pexels.com
孔庆龙,一线架构师,具有多年的金融架构经验,具备 SOA 服务化、服务治理、系统优化、分布式系统项目经验。目前关注于互联网金融技术架构设计、分布式架构、微服务架构、DevOps 实践、大数据等领域。
1
什么是系统优化
系统优化,一方面是系统化地对 IT 系统或交易链上的每个环节进行分析并优化,另一方面是对单一系统进行瓶颈点分析和优化。这两方面的优化目标大致相同,无非是提高系统的响应速度、吞吐量、降低各层耦合,以灵活应对市场需要。系统优化主要在如下三个层面上进行。
IT 系统治理层: 这一层面的优化不只是性能优化,还包括为适应业务架构变化而带来的应用架构优化(如应用分层、服务治理等)。
系统层: 这一层面的优化包括业务流程优化、数据流程优化(如提高系统负载、减少系统开销等)。
基础设施层: 这一层面的优化主要是提高 IaaS 平台的能力(如使弹性集群具备横向扩展能力、支持资源快速上下线和转移等)。
2
系统优化的方法论、思路和原则
什么是方法论,我个人的理解就是听起来很厉害,做过的人认为是废话,但可以指明行动方向的理论。下面就根据多年经验,从系统优化的常用方法论、优化思路和优化原则三方面进行简单分享。
2.1
常用方法论
常用的方法论有以下几条。
阿姆达尔定律(Amdahl Law): 根据公式 S=1/(1−a+a/n)分析每次对整体效果影响最大的点,并进行优化。
不访问不必要的数据: 减少交易线上的不必要环节,减少故障点和维护点。
就近加载,缓存为王。
故障隔离: 不要因为一个系统瓶颈压垮整个交易平台。
具备良好的扩展能力: 合理地利用资源,提高处理效率并避免单点故障。
对交易链进行优化,提高吞吐量:减少串行同步调用,合理拆分(垂直/水平拆分),规则前置。
性能和功能同等重要: 若交易链上有 5 个性能变为设计阶段时的 90%,则整体性能会变为设计时的 59%。
2.2
优化思路
根据以往经验总结出系统优化的思路,大致如下:
(1)了解现状,发现问题。
(2)确定清晰的优化目标,分析现状与目标的差距并确定执行路线。
(3)对系统进行拆分,分别对逻辑层(Web 层、业务层、持久化层)和物理层(客户端、网络、应用服务器、数据库服务器)进行优化。
(4)利用工具对系统进行监控和测试,并对监控和测试结果进行分析。
(5)科学地对系统进行优化。
遵循一定的程序: 监控/性能测试→分析瓶颈→罗列瓶颈的因素→验证瓶颈因素 →修改系统→确认是否达到优化目标。
确定影响性能的因素: CPU、内存、I/O、网络或其他因素。
找出主要的瓶颈,优先解决关键因素,再重复监控或测试验证。
避免过度优化,一次修改一个瓶颈,不要对不需要的地方进行优化。
提高 CPU 性能: 写出更快的代码,设计出更好的算法,减少短期生存的对象。
提高内存性能: 减少长期生存的对象。
提高 I/O 性能: 重新设计应用,减少 I/O 的交互。
缓存为王: 适度缓存,做到最大化发挥数据库缓存、应用端缓存、客户端缓存的作用。
2.3
优化原则
系统优化的原则如下:
在应用系统的设计、开发过程中,应当始终把性能放在考虑的范围内。
确定清晰明确的性能目标是关键。
性能优化是伴随整个项目周期的,最好分阶段设定目标,在达到预期性能目标之后,即可对本阶段工作进行总结和知识转移,进入下一阶段的优化工作。
必须保证优化后的程序可以正确运行。
性能更大程度上取决于良好的设计,优化技巧只是一个辅助手段。
优化过程是迭代渐进的过程,每次优化的结果都要反馈到后续的代码开发中。
性能优化不能以牺牲代码的可读性和维护性为代价。
3
性能优化
3.1
常见的性能问题
常见的性能问题大多数是由于资源不足或热点资源竞争导致的,下面将从客户端性能、 J2EE 系统性能和数据库性能三方面进行简单介绍。
常见的客户端性能问题
常见的客户端性能问题有如下几项。
加载慢: 第一次启动慢或者重新加载慢。
无响应: 事件突发导致页面假死。
受网络带宽影响严重: 因为需要下载大量资源文件,所以在一些网络环境不好的地区页面加载缓慢。
JS(JavaScript)内存溢出: 频繁地对对象属性进行操作,造成内存被大量占用,最终溢出。
常见的 J2EE 系统性能问题
常见的 J2EE 系统性能问题有如下几项。
内存泄漏:在运行过程中,内存不断被占用而不能被回收,内存使用率随着时间的推移或负载的增加呈线性增长,系统处理效率随着时间的推移或并发的增加而下降,直至将分配给 JVM 的内存用尽。
资源泄漏:将资源打开后未关闭或未成功关闭而导致的问题。这些资源包括数据源连接、文件流等。当这些资源经常被打开而未能成功关闭时,就会导致资源泄漏。数据源连接泄漏是常见的资源泄漏问题。
过载:过度使用系统,超出其所能承受的负荷。
资源瓶颈:资源被过度使用或分配不足而引起资源瓶颈问题。
线程阻塞、线程死锁:线程执行某段代码时,无法获得相关的同步锁而造成通信阻塞。
应用系统响应慢:由于应用算法未优化,不合理地读取大量数据,SQL 缺少索引或存在过多表关联而导致响应时间过长,执行变慢。
应用系统不稳定:应用系统的响应出现时快时慢的现象。
应用系统中有各种各样的异常情况发生:有些是中间件服务器抛出的异常,有些是数据端抛出的异常。
常见的数据库问题
在以往做核心业务系统技术支持时,遇到的常见的数据库问题如下所述。
死锁:由于联机服务过早开启数据库事务,第三方服务未及时响应,使得锁和事务无法提交而导致数据库死锁。
I/O 繁忙:由于存在不良 SQL 或业务逻辑设计不合理而导致大量 I/O 等待。
CPU 使用率居高不下:由于高并发或缓存穿透而导致数据库 CPU 居高不下或忽高忽低。
3.2
性能优化的具体工作
“天下武功,唯快不破”,性能优化的首要工作就是提高系统的响应时间(响应时间 = 服务处理时间 + 排队时间)。如图 16.1 所示的经典响应时间曲线,我们要做的就是通过优化程序来减少服务时间,通过提高系统的吞吐量减少系统的排队时间。图 16.1 中的纵轴代表的是响应时间,即服务时间和排队时间的总和; 横轴代表的是到达率。随着每单位时间进入系统的事务数的增加,曲线逐渐向右滑动。随着到达率的增加,排队时间会在某一时刻陡然上升,此时,响应时间也将陡然上升,性能下降,而用户会感到非常沮丧。
图 16.1
下面通过以往项目中的案例来分析性能优化的具体工作。
交易线优化
交易线用来从服务消费者的角度查看交易在各个层面上应该完成的功能,以及功能点之间的关系。功能点之间的关系用有向路径来表示,如图 16.2 所示。
交易线优化的原则如下:
最短路径: 减少不必要的环节,避免故障点。
交易完整性: 通过冲正或补偿交易等确保交易线各环节的事物一致性。
故障隔离和快速定位: 屏蔽异常情况对正常交易的影响,通过交易码或错误码快速定位问题。
流量控制原则: 可以对服务通道进行流量控制,并结合优先级设置优先处理级别高的业务。
超时控制漏斗原则: 尽量使交易线上前端系统的超时设置大于后端系统。
熔断和故障隔离原则: 避免由于某个服务提供者出现故障而导致整个交易线不可用。
随着架构的演变,现在已经由竖井式系统逐步发展为以服务为单元、可灵活构建的分布式服务体系,如图 16.3 所示。在服务治理的过程中,原来的核心业务系统被打碎为各种独立的业务组件,一些中间层平台型系统基于这些业务组件和流程服务逐渐构建了业务服务,并为前端应用的快速构建提供业务支撑。在这个过程中,服务识别和构建是基础,交易线的规范是保障,通过交易线规范可以确定服务治理的涉及范围,因为在软件版本迭代时,很少有人能把系统的全部细节都考虑清楚,所以要靠规范来确定治理范围,而不是靠人。
要开发一个订单查询功能 A,服务整合平台的 B 和 C 两个服务都可以完成此功能的开发,不同的是 B 在 C 的基础上增加了一些额外的校验。按照最短路径原则,A 应该直接调 用 C 服务,如图 16.4 所示。
流量控制的目的之一是保证各系统健康稳定地运行。一般使用计数器按照交易类型来检测交易的并发数,不同交易类型使用不同的计数器。当交易请求到达时,计数器加 1; 当请求响应失败时,计数器减 1。
流量控制是对服务提供者的一种保护机制,那么服务消费者如何避免因为服务提供者不可用而导致自身不可用,并逐级向交易链的调用方放大这种不可用,最终拖垮整个交易链路导致雪崩的情况呢? 服务的消费方一般可以通过以下 3 种方式来防止“雪崩”,实现对交易链路的保护。
隔离机制:资源池隔离。如果将线程池、数据库连接池等独立分配,那么即使某类 服务出现问题也只会影响单独的资源池。
熔断机制: 当调用失败,触发预设的阈值时,应快速返回预设结果,避免大量的同步等待,熔断侦测请求会定期检测服务状态,进行服务状态转换。
监控报警: 对服务调用状态进行监控并设置预警值,在发生异常时可以及时通知相关人员进行干预处理,缩短异常影响范围。
客户端优化
从 Web 请求时序(如图 16.5 所示)中可以看出,客户端优化的首要目标是加快页面展 现和响应速度,其次是减少对服务器端的调用,常见的解决办法如下:
分析瓶颈点,有针对性地进行优化。
缓存为王,通过在客户端缓存静态数据提升页面响应时间。
通过 gzip、deflate 压缩来减少客户端网络的下载流量。执行压缩的好处是可以减少 60%左右的文本类文件所占用的空间,但执行时需要注意解析 HTTP 请求的, Accept-Encoding 头判断是否支持压缩,并在响应中设置 Content-Encoding 编码格式。
使用压缩工具对 JS 进行压缩,减小 JS 文件所占用的空间。
删除、合并脚本、样式表及图片,减少 GET 请求。*无阻塞加载 JS。
预加载图片、CSS 样式、JS 脚本。
按需加载 JS 脚本。
优化 JS 处理方法,提升页面处理速度。
图 16.6 所示的是某企业内部应用系统客户端的 HTTP 请求监控记录。
从图 16.6 中可以看到共计发送 25 次请求(21 次命中缓存、4 次与服务器端交互)。从 图 16.7 所示的统计信息中可以看到: 请求耗时总计 5.645s,进行了 4 次网络交互,接收到 5.9KB 数据,发送了 110.25KB 数据,使用 gzip 压缩节省了 8KB 数据。
通过优化后端请求、合并和压缩 JS/JSP 文件等操作,将页面响应时间优化到 2s 左右。
进行前端优化最好了解浏览器原理、HTTP 原理。
服务器端优化
服务器端的涉及面比较广,图 16.8 整理了在进行服务器端优化时可能存在的问题,以及所采用的辅助分析工具、分析思路、常见解决办法。
(未完待续)
本文节选自中生代技术社区丛书之《架构宝典》
精彩文章推荐: