如果第二次看到我的文章,欢迎「文末」扫码订阅我的个人公众号「跨界架构师」)哟~
每周五11:45 按时送达。当然了,也会时不时加个餐~
我的第「124」篇原创敬上
大家好,我是Z哥。
好久没写技术文章了,最近正好有进行一些思考,顺手写出来分享给大家。
上了一定规模的系统,特别是To C的系统,性能优化或多或少都会被逼着去做一下。否则,系统便无法支撑业务的发展,技术成了拖后腿,不是引领业务了。
一旦线上出现了性能问题,就会很棘手。因为它和业务功能上的Bug不同,后者的分析和解决思路更清晰,只要日志记录到位,沿着一条已知的业务逻辑线,很容易就能找到问题根源。
而性能问题就会复杂的多,导致的因素有很多,甚至会是多种因素共同作用下的结果。比如,代码质量低下、业务发展太快、架构设计不合理等等。
而且一般情况下,性能问题处理起来比较耗时,涉及到的分析链路可能会很长,特别是自己小组之外的上下游系统,很多人不愿意干,或者说有心无力。最多采用一些临时性的补救手段,碰碰运气。比如,扩容增加机器、重启大招、……。
有些临时性的补救措施,有时候不但不能解决问题,还会埋下新的隐患。
比如,从表象上看到某个程序因为给的资源不足导致产生性能问题。临时增加更多资源给它,可能从表面上看,问题是解决了。但是实则可能是因为程序内部对资源的使用上存在不合理的地方,增加资源只是延缓问题发作的时间,而且还可能会侵占其它程序的运行资源。
为了避免陷入如此的窘境,我们应当尽量提前进行性能优化,未雨绸缪。甚至最好是将它作为一个周期性的工作来进行。
接下去就来分享一下我对做性能优化的思路。
/01 明确优化目的/
很多人优化优化着慢慢变成了为了优化而优化,目的丢了,或者甚至一开始就没考虑过。如此会陷入到无意义的性能黑洞中,无法自拔,只是不断追求更好看的性能指标。
优化的目的可以是增强用户体验,比如消除一些有明显卡顿的页面和操作。还可以是节省服务器带宽流量、减少服务器压力这些。无论如何,你需要有一个目的。
/02 定标准,做到什么程度/
优化这事是永无止境的,为了避免陷入到前面说的无意义的性能黑洞中,我们最好能够根据实际的业务情况定义出一个相对客观的标准,代表优化到什么程度。
我自己惯用的标准是确保比预期高50%,如果条件允许则争取到100%。
比如,根据当下的性能指标与业务量对比,发现最大并发数可能会超过当前的2倍,那么此时优化到争取优化提升3倍,至少保证能提升2.5倍,是一个比较合理的标准。
之前专门写过一篇关于容量预估的文章《做「容量预估」可没有true和false》,可以在文末跳转过去看下,这里就不展开了。
/03 找到瓶颈点/
很多人做优化的时候,逮着代码就开始改。的确,只要有一定的知识积累,很容易就能从代码中发现,写法A不如写法B这样的代码。
但其实大部分情况下,「流程上的优化远胜于语法级别的优化」。比如将每一个字符串拼接改成用StringBuilder来实现,大多数情况下带来的成果其实很小,甚至在某些情况下还不如不改。
所以,我们最好还是能够借助一些客观数据,以获得更多的运行环境相关的信息,来找到整个“木桶”上最短的一块“板”。如整个系统的总体架构、服务器的信息等,便于定位到底性能的瓶颈点在哪。
「流程上的优化远胜于语法级别的优化」中的“流程”除了业务流程之外,还包括技术层面的流程,比如数据在网络中的流转过程。
/04 着手优化/
最后才是着手优化。
做优化的时候需要避免两个常见的误区。
第一,不要过度追求应用的单机性能,如果单机表现良好,还应该从整体的角度去思考。
第二,不要过度追求单一维度上的极致优化,比如过度追求 CPU 的性能而忽略了内存方面的瓶颈。
正确的思路一般符合下面两个方向。
第一,空间换性能。一个节点顶不住就多复制一个节点出来,独一份的数据导致资源竞争得厉害,就多复制一份数据出来。
第二,距离换性能。数据从服务端经过层层处理返回到客户端觉得慢的话,那么能不能直接保存在客户端,或者至少是离客户端尽可能近的地方。
好了,思路清楚了,具体在做的时候我建议你根据下面小标题的顺序进行。不管是主动地性能优化,还是被动地排查性能问题都一样。
/01 应用程序层面/
不管你愿不愿意承认,现实中的大部分性能问题皆是应用程序自身部分的代码导致的。
我们总是不太愿意承认自己的错误,我见过太多程序员总是习惯性的将问题先归结于硬件问题,网络问题等等,然后最终排查下来的根源往往还是在coding的应用程序上。
所以,我们更应该先从应用程序本身入手进行分析。而且,应用程序所处的位置更「上游」,可操作性更强,让我们可以有更多的手段进行优化。
01 缓存
首先,最常见的便是「缓存」,这是用空间换性能的经典。
数据必然是存储在非易失性的数据库中的,但是一些会被高频访问的数据,将它从数据库中复制一份,存储在易失性的内存上做缓存,可以大大提高被访问的性能。这个道理大家都懂,就不多说了。
但是值得提醒的一点是,缓存数据的数据结构设计很重要,没有一种数据结构是万能的。需要更多的权衡,因为数据结构设计的越简单、单一,缓存数据的二次运算就越多;反之,所有都存储「结果数据」的话,需要冗余的数据量又过大(缓存数据更新还麻烦)。
还得提醒一点,如果缓存的数据量不小,还得考虑增加一个缓存淘汰算法,否则缓存命中率不堪入目,白白浪费大量内存资源。
之前的《分布式系统系列》中有几篇缓存相关的聊了很多细节,可以在文末跳过去查阅。
02 异步
举个现实生活中的例子,如果你在手机上点了一杯奶茶,去店里拿的时候发现前面还有20个号,你会在这干等半小时么?
我想大部分人都不会吧,宁愿去别的地方溜溜。异步就是通过避免“干等着”来提升性能的手段。
做异步主要是以下两种方式,
-
通过线程进行异步。这主要用于涉及到I/O的地方,像磁盘I/O和网络I/O。一旦产生I/O其实就意味着背后的操作是由另外一个程序在进行,此时CPU就不用空着了,让它忙别的去吧。
-
通过中间件异步,比如MQ。这用于更大的场景里,比如在某些流程中、上下游系统的衔接中,如果有些结果并不需要实时收到,那么通过MQ进行异步就可以大大提高性能。毕竟MQ的性能更接近NoSQL,性能自然比关系型数据库高的多。更何况,还将一些业务逻辑的预算给滞后了,当下的性能会更好。
03 多线程&分布式
这两点都是「分治」思想的体现。一个快递员送1000个包裹比较慢,那么让10个快递员同时各送100个自然就快了。
但是切勿分的太狠,毕竟,多起一个线程相当于多一个放养的娃,放出去太多的话,管理成本很高,可能反而会更慢。这就是线程切换的成本,分布式系统中也存在类似的管理成本。
不过,一个小建议送给你。不到迫不得已,能通过「单机多线程」应付的,就不要引入分布式了。因为,网络这个东西实在太不靠谱了,你得为它做大量的额外工作。
04 延后运算
这个和缓存的思路相反,将一些运算尽可能的延后到用的时候。适用的场景也和缓存相反,适用于一些低频的、运算耗时的数据上。
延迟加载、插件化等等就是该思想的体现。
05 批量,合并
如果你需要在短时间内频繁的传递多个数据给同一个目的地,那么尽量考虑将他们打包到一起,一次性传输,特别是涉及到I/O的场景。
如果手头的系统还是一个单点系统,这招的性价比就非常高。在避开分布式系统的复杂性的前提下,获得性能提升。
数据库的bulk操作,前端的sprite图,都是该思想的体现。
应用程序层面的其它优化方式还有很多。比如,用长链接代替频繁打开关闭的短链接、压缩、重用等等。这些相对比较简单和好理解,就不多说了。
应用程序层面的事情做到位了之后,我们再来考虑组件层面的优化。
/02 组件层面/
组件是指那些非业务性的东西,比如一些中间件、数据库、运行时的环境(JVM、WebServer)等。
数据库的调优,总的来说分为以下三部分:
-
SQL语句。
-
索引。
-
连接池。
其它的一些,比如JVM的调优最主要的就是对「GC」相关的配置调优。WebServer的调优主要是针对「连接」相关的调优。这些细节就不赘述了,资料多到看不过来。
/03 系统层面/
系统层面的一些调优工作,涉及到运维工程师的一些工作,我不是很擅长就不误人子弟了。但是我们可以借助系统层面的一些技术指标来观测并判断我们的程序是否正常。比如,CPU、线程、网络、磁盘、内存。
01 CPU
判断CPU是否正常,大多数情况下关注这三个指标就够了,CPU利用率、CPU平均负载、CPU上下文切换。CPU利用率大家基本上都知道,就不多说了,那就说说后面两个。
关注CPU平均负载的时候,特别需要注意趋势的变化。如果 1 分钟/5 分钟/15 分钟的三个值相差不大,那说明系统负载很平稳,则不用关注,如果这三个值逐渐降低,说明负载在渐渐升高,需要排查具体的原因。
CPU上下文切换。上下文切换的次数越多,就意味着更多的CPU时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,真正进行你所期望的运算工作的时间就越少,系统的整体性能自然就会下降。导致这个情况的原因主要有两点,
-
程序内的磁盘I/O、网络I/O比较多。
-
程序内启动的线程过多。
02 线程
线程方面除了关注线程数之外,还需要关注一下处于「挂起」状态的线程数量有多少。
挂起状态的线程数过多,意味着程序里锁竞争激烈,需要考虑通过其它的方案来缩小锁的粒度、级别,甚至是避免用锁。
03 网络
通常在硬件层面内网带宽会远大于外网的带宽,所以,外网带宽被吃满的情况更加常见,特别是多图、多流媒体类型的可对外访问系统。关于流量大小相关的问题一般大家都能想到,就不多说了。
但是,Z哥提醒你要特别关注端口的使用和每个端口上的连接状态情况。比较常见的问题是,连接用完有没有及时释放,导致端口被占满,后续新的网络请求无法建立连接通道。(可以通过netstat、ss获取网络相关的信息。)
04 磁盘
除非是规模非常大的系统,否则一般情况下,从磁盘的指标上看不出啥问题。
平时看的时候,除了看看利用率、吞吐量和请求数量之外,有两个容易被忽略的点可以多关注下。
第一点,如果I/O利用率很高,但是吞吐量很小,则意味着存在较多的磁盘随机读写,最好把随机读写优化成顺序读写。(可以通过 strace 或者 blktrace 观察 I/O 是否连续判断是否是顺序的读写行为)
其次,如果I/O等待队列的长度比较大,则该磁盘存在 I/O 性能问题。一般来说,如果队列长度持续超过2就可以这么认为。
05 内存
关注内存的时候除了内存消耗之外,有一个Swap换入和换出的内存大小需要特别注意一下。因为Swap需要读写磁盘,所以性能不是很高。如果GC的时候遍历到的对象恰巧被Swap 出去了,便会有磁盘I/O产生,性能自然会下降。所以这个指标不应该太高。
大多数内存问题,都和对象常驻内存不及时释放有关,有很多工具可以观察对象的内存分配情况。如,jmap、VisualVM、heap dump等。
如果你的程序部署在linux系统上的话,不得不错过Brendan Gregg的大神整理的精华。下面就引用一张图,给大家感受一下,具体可以去 http://www.brendangregg.com/linuxperf.html 自行查阅更多相关的内容。
▲图片来自于brendangregg.com
最后,虽然性能优化是一件大家都知道的好事,但是再好的事做起来都有成本。所以,如非必要,不要过早、过度进行性能优化哦。
好了,总结一下。
这篇呢,Z哥和你聊了一下非常让程序员们头疼的程序性能问题。想要避免受这个问题困扰的前提是事前做好性能优化工作。
做性能优化不能走一步算一步。事先需要做三件事「明确优化目的」、「定标准」、「找到瓶颈点」。
具体做优化的时候建议从应用程序层面开始,再到组件层面,最后才是系统层面,从上往下,层层深入。顺带分享了每个层面的常用一些方法和思路。
希望对你有所启发。
在一个大系统中,数据就像水,整个系统就像是一个漏斗,漏斗的每一层代表每个子程序。上层的子程序对性能的损耗越低,能流下去的水就越多,直到最后一层「数据库」处,也可以理解为是存储。
所以,赶紧行动起来,开启保卫数据库之战吧。
推荐阅读:
作者:Zachary
出处:https://zacharyfan.com/archives/1051.html
如果你喜欢这篇文章,可以点一下左下角的「推荐」。
这样可以给我一点反馈。: )
谢谢你的举手之劳。
▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描下方的二维码~。
如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。
如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。
定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些深度思考。
相关推荐
### Eclipse 开发性能优化与 Java 代码性能优化 在日常的软件开发过程中,尤其是在使用 Eclipse 进行 Android 开发时,经常会遇到 IDE 运行缓慢的情况。为了提高开发效率,本文将详细介绍 Eclipse 开发环境及 Java ...
### Java代码性能优化23种技巧详解 #### 一、避免在循环条件中使用复杂表达式 在Java中,尤其是在不进行编译器优化的情况下,循环条件会在每次迭代时都被重新计算。如果循环条件涉及复杂的表达式或者动态计算,这...
在C代码优化中,遵循80-20原则至关重要,...通过这些策略,可以有效地优化C代码,提高程序性能,尤其针对关键的20%代码段,优化的效果更为显著。然而,优化不仅仅是技术问题,还需要结合具体应用场景和性能需求来权衡。
这个“优化算法测试函数MATLAB代码完整版”压缩包提供了全面的资源,包括代码、数据以及算法的详细描述,旨在帮助用户理解和应用各种优化算法。 首先,MATLAB是一个强大的数学计算软件,广泛用于科学计算、图像处理...
【性能优化的修改方法】 在IT领域,性能优化是提高系统效率、降低资源消耗的关键环节。本篇将从几个核心方面介绍如何进行性能优化,包括数据库优化、应用服务器优化、减少与服务器的交互以及SQL语句的编写建议。 1...
总的来说,“Java程序性能优化 让你的Java程序更快、更稳定”这本书将涵盖以上诸多方面,通过理论结合实际的示例代码,帮助读者深入理解Java性能优化的各个方面,从而写出更快、更稳定的Java程序。书中附带的源文件...
贝叶斯优化是一种在复杂...如果你打算深入研究或应用这个技术,"贝叶斯优化全套代码—改-添加使用教程"将是一个非常有价值的资源。通过阅读和实践,你将能够熟练掌握这一强大的优化工具,提升你的机器学习项目的效果。
性能优化可能包括但不限于代码优化、垃圾收集调优、内存管理、线程优化等多方面。Java性能优化的目的是为了让程序运行更快、更稳定,同时降低对系统资源的占用。 #### 常见Java性能问题分析 - 内存泄漏:由于对象...
### 软件工程中的代码重构与性能优化 #### 第一章:简介 **代码重构与性能优化概述** - **代码重构**:是指在不改变软件外部行为的前提下,对内部结构进行调整,以提高代码质量的过程。这通常涉及到对代码结构的...
针对web前端性能低下的问题,王成、李少元、郑黎晓、缑锦、曾梅琴、刘慧敏等学者系统地提出了一套旨在提高网页加载速度、呈现速度和用户体验的完整Web前端性能优化解决方案。该方案涵盖了服务器端优化、HTML优化、...
- **目标代码优化**:针对特定平台或处理器的特点进行优化,可以进一步提升性能。 4. **优化范围**: - **局部优化**:以基本程序块为单位进行优化。 - **循环优化**:针对循环结构进行优化,可以大幅提高性能。...
### ASP.NET性能优化详解 在IT领域,尤其是Web开发中,ASP.NET作为微软推出的一款用于构建动态网页的应用...当然,性能优化是一个持续的过程,开发者应根据实际应用情况,不断调整和优化代码,以达到最佳的运行效果。
在讨论Swift性能优化时,首先需要了解的是Swift与Objective-C的性能比较。由于Swift是一种相对现代的语言,与拥有几十年历史的Objective-C相比,Swift在程序执行速度方面表现出了更高的性能。这一点在DeltaBlue和...
1. 设置上的优化:在Simulink模型的配置参数中,可以调整代码生成选项来优化性能。例如,启用“优化”选项可以减小代码体积,提高执行速度。同时,还可以调整实时工作空间的设置,如内存使用和计算精度。 2. 优化...
### 软件工程中的代码重构与性能优化方法研究 #### 第一章:软件工程概述 **软件工程简介** 软件工程是一门集成了系统化、规范化的原理与方法的学科,旨在有效地进行软件产品的开发、运行及维护。自20世纪50年代...
4.服务器高并发性能优化( linux ,windows 服务器性能优化提升) 5.C#前端后端代码自动生成(netframework ,netcore) 6.服务器安全(端口号扫描,文件权限,防火墙,服务) 7.web应用程序5级缓存优化 8.程序...