系统性能优化是一项复杂而又重要的工作,是任何一个系统都绕不开的话题。
这里说的性能优化是指在一定的资源限制前提下,对系统的响应时间、吞吐量做优化。
优化的好呢,拖拉机变超跑,优化不好呢,也有可能拖拉机变板儿车。
今天我们就来捋一捋,系统性能优化到底需要关注哪些点,可以从哪些层面入手做优化?
首先是代码层面,任何一个系统都是代码一行行构建起来的,代码质量决定了系统的性能基准。这方面的提升,主要还是看工程师对编程语言的经验积累,包括对依赖类库的熟练程度。
比如拿序列化组件来说,序列化时,fastjson更快,反序列化时,jackson更快。结合具体场景选不同组件。
其次是中间件层面,一般中间件都会对外暴露参数,对这些参数的理解,也会很大程度影响性能。
比如是否开启压缩,如果你CPU资源充足,传输内容又很大时,优化效果就比较明显。
再比如连接池的配置,是长连接还是短连接?是同步还是异步?
就Java语言来说,JVM调参也属于这一层面,需要你对JVM结构、垃圾回收机制有较为深刻的理解。
然后是系统架构设计层面的优化,这里的套路就比较多了,一般我经常会关注下面5点:
1、合理的模块依赖关系,一个请求依赖路径上的模块是否是达到最简?
比如,一个业务请求,之前需要123456步完成。经过业务梳理,你发现,其实必须当时完成的只有123前三步。4是更新汇总表,可以去掉,改为定时任务来更新。5是写日志,可以改为异步写,6是通知其他相关系统,可以改为发消息到队列,不必同步等待结果。这样原来需要6步操作的请求,就一下被压缩到了3步,响应时间大大缩短。
2、如果请求路径上已经不能再减,那能否并行呢?比如一个请求中的两个操作,如果它们没有先后依赖关系,可以分别启两个线程一起去执行,等待结果后,再一起返回即可。但如果这两个操作,都是CPU计算,我们一般不会去并行。因为CPU的核心线程数有限,CPU计算操作并行后,看似当前请求的CPU利用率更高,但其实每个请求都这么抢,没什么意义,反而会增加线程切换成本,让系统的吞吐量在高压下,衰败的更快。
3、缓存的使用,缓存是典型的空间换时间的方法。可以根据需要建立不同级别的缓存,如本地缓存、分布式缓存(如Redis)来提升系统性能。但这里需要关注缓存的过期时间,做好数据时效性和访问效率之间的平衡。此外,Redis这种分布式缓存,也可以采用主动更新的方式,但本地缓存一般不这么干。其实,前端常用的CDN,本质上也是一种缓存,是对静态数据的访问提速方案。
4、池化,连接可以池化变连接池,线程可以池化变线程池,所有创建成本高的对象,都可以考虑预建的方式,减少等待时间。这个具体参照我上一期关于连接池的讨论。
5、如果是机器学习类应用,还可以考虑异构计算,用GPU给计算密集型任务提速。当然有些CPU也支持SIMD,也就是单指令,多数据流,但这个还要看具体编程语言的支持。
说完了系统设计,最后我们还需要关注数据库层面的优化。关系型数据库重点关注索引的建立和SQL的编写,非关系型要结合具体产品做优化,比如Mongo的分区设计,Hbase的rowkey设计。
说了这么多,我们来稍微总结一下,为什么我们有时觉得系统优化复杂又困难重重呢?那是因为我们需要关注的点太多了,如果没有抓住系统的核心问题,就往往适得其反。
其实,系统性能优化,核心思想就两个:一是节约,二是平衡。
减少请求依赖是节约、优化代码执行路径也是节约,因为性能优化就是在有限资源前提下进行的。首先要做的就是要尽可能的减少不必要的消耗。
在节约的前提下,下一步就是平衡。系统性能上不去,就是因为在某个方面遇到了瓶颈点。可能是CPU计算资源、可能是内存资源、也可能是IO资源。我们优化就是要去消灭这一个个的瓶颈点。去往相对不紧张的资源去转化,去平衡。
开启压缩,是提高了CPU计算成本,但减少了传输成本;建立缓存,是提高了内存成本,但降低了数据访问成本;池化,是提高了内存成本,但降低了CPU计算成本。
对机器各种资源的消耗整体平衡了,没有瓶颈点了,那这台机器的价值才算被我们榨干了。系统的整体吞吐量才能达到最大。
懂了核心思想,灵活运用上面的优化方法,再配合一两件性能分析工具,系统性能优化就能无望而不利了。
来,拿好你的超跑钥匙~