深入浅出 -- 系统架构之性能优化的核心思维

"在当前的互联网开发模式下,系统访问量日涨、并发暴增、线上瓶颈等各种性能问题纷涌而至,性能优化成为了现时代开发过程中炙手可热的名词,无论是在开发、面试过程中,性能优化都是一个常谈常新的话题"。Java语言作为企业应用中的"抗鼎者",Java生态中也积攒了大量宝贵的性能优化经验。

在应用系统中,性能优化其实可以从各个角度出发考虑,如架构优化、前端调优、中间件调优、网关调优、容器调优、JVM调优、接口调优、服务器调优、数据库调优等,从优化类型上而言,主体可以分为三类:

  • 结构/架构优化:优化应用系统整体架构做到性能提升的目的。如:读写分离、集群热备、分布式架构、引入缓存/消息/搜索中间件、分库分表、中台架构(大数据中台、基础设施中台)等。
  • 配置/参数优化:调整应用系统中各层面的配置文件、启动参数达到优化性能的目标。如:JVM、服务器、数据库、、操作系统、中间件、容器、网关参数调整等。
  • 代码/操作优化:开发者编写程序时,从代码、操作方面进行调节,达到效率更高的初衷。如:代码中使用更优秀的算法思想/设计模式、SQL优化、对中间件的操作优化等。

本章则重点阐述Java中,JVM虚拟机相关的全面优化,如:内存、GC、即时编译、JVM参数配置等。

一、系统中性能优化的核心思维

性能调优,是建立在经验的基础之上才能做好的,对于调优要实事求是,任何的调优手段或技巧不要纸上谈兵,只有经过实践的才能用于生产环境,千万不要将一些没有实际依据的调优策略用于线上环境,否则可能会导致原本好好的程序反而调优调崩溃。

1.1、单个节点层面调优的核心思想

在一个程序中,所有的业务执行实体都为线程,应用程序的性能跟线程是直接挂钩的。而程序中的一条线程必须要经过CPU的调度才可执行,线程执行时必然也会需要数据、产生数据,最终也会和内存、磁盘打交道。因而单个节点的性能表现,不可避免的会跟CPU、内存、磁盘沾上关系。

线程越多,需要的CPU调度能力也就越强,需要的内存也越大,磁盘IO速率也会要求越快。因此CPU、内存、磁盘,这三者之间的任意之一达到了瓶颈,程序中的线程数量也会达到极限。达到极限后,系统的性能会成抛物线式下滑,从而可能导致系统整体性能下降乃至瘫痪。

由于如上原因,在考虑性能优化时,必然不能让CPU、内存、磁盘等资源的使用率达到95%+,一般而言,最大利用率控制在80-85%左右的最佳状态。

同时,前面也分析过,因为程序的性能跟线程挂钩,所以线程的模型也是影响性能的重要因素。目前程序设计中主要存在三种线程处理模型:BIO、NIO、AIO(NIO2)BIO是Java中传统的线程一对一处理模型,NIO的最佳实践为reactor模型,而proactor模型又作为了NIO2/AIO的落地者。绝大部分情况下,AIO的性能优于NIO,而NIO的性能又远超于BIO

所以在做性能优化时,你应该要清楚系统的性能瓶颈在哪儿,到底是要调哪个位置?是线程模型?或是CPU调度?还是内存回收?亦是磁盘IO速率?针对不同层面有不同的优化方案,并非为了追求"热词/潮流"而盲目的调优。

1.2、优秀且适用的系统架构胜过千万次调优

一个单体架构(Tomcat+MySQL)部署的系统遇到性能问题时,能力再强,本事再大,任凭使出浑身解数也无法将其调到处理万级并发的程序,正常服务器部署的一台MySQL服务做到极致调优也难以在一秒内承载5000+QPS。一味的追求极致的优化,其实也难以解决真正大流量下的并发冲击,因此一套优秀的系统架构胜过自己千万次的调优。

当然,也并非说项目实现时,越多的技术加进来越好,一套完善的分布式架构就必然比单体架构要好吗?其实也不见得,因为当引入的技术越多,所需要考虑的问题也会更多,耗费的成本也会越高,一个项目收益60W,结果用上最好的配置(高端的开发者+顶级的服务器+完善的分布式架构)成本耗费200W,这值得吗?答案显而易见。因此,并没有最好的技术架构,只有最适用的架构,能从现有环境及实际业务出发,选用最为合适的技术体系,这才是我们应该做的事情。如:

  • 项目业务中读写参半,单节点难以承载压力,项目集群、双主热备值得参考。
  • 项目业务中写大于读,引入消息中间件、DB分库、项目集群也可以考虑。
  • 项目业务中读大于写,引入缓存/搜索中间件、动静分离、读写分离是些不错的选择。
  • .......

当你的系统原有架构遇到性能瓶颈时,你甚至可以考虑进一步做架构优化,如:设计多级分布式缓存、缓存中间件做集群、消息中间件做集群、Java程序做集群、数据库做分库分表、搜索中间件做集群.....,慢慢的,你的系统会越来越庞大复杂,需要处理的问题也更为棘手,但带来的效果也显而易见,随着系统的结构不断变化,承载百万级、千万级、亿级、乃至更大级别的流量也并非难事。

但只有当你的业务流量/访问压力在选用其他架构无法承载时,你才应该考虑更为庞大的架构。当然,如果项目在起步初期就有预估会承载巨大的流量压力,那么提前考虑也很在理,采用分布式/微服务架构也并非失策,因为对比其他架构体系而言,微服务架构的拓展性更为灵活。但也需要记住:分布式/微服务体系是很好,但它不一定适用于你的项目。

1.3、预防大于一切,调优并非"临时抱佛脚"

当问题出现时再想办法解决,这种策略永远都属于下下策,防范于未然才是最佳方案,提前防范问题出现主要可分为两个阶段:

  • ①项目初期预测未来的流量压力,提前根据业务设计出合适的架构,确保上线后可以承载业务的正常增长。
  • ②项目上线后,配备完善的监控系统,在性能瓶颈来临前设好警报线,确保能够在真正的性能瓶颈到来之前解决问题。

对于项目初期的架构思考,值得牢记的一点是:不要"卡点"设计,也不能过度设计造成性能过剩,举例:

项目上线后的正常情况下,流量大概在"一木桶"左右,结果你设计时直接整出个"池塘"级别的结构出来了,这显然是不合理的,毕竟架构体系越庞大,项目的成本也自然就越高。

当然,也不能说正常情况下压力在"一木桶"左右,就只设计出一套仅能够承载"一木桶"流量的结构,这种"卡点"设计的策略也是不可取的,因为你需要适当考虑业务增长带来的风险,如果"卡点"设计,那么很容易让项目上线后,短期内就遭遇性能瓶颈。

因此,如果项目正常的访问压力大概在"桶"级别,那将结构设计到"缸"级别是合理的,这样即不必担心过度设计带来的性能过剩,导致成本增高;也无需考虑卡点设计造成的:项目短期遭遇性能瓶颈。

但设计时的这个度,必须由你自己根据项目的业务场景和环境去思量,不存在前篇一律的方法可教。

有人曾说过:"如果你可以根据业务情景设计出一套能确保业务增长,且在线上能稳定运行三年时间以上的结构,那你就是位业内的顶尖架构",但老话说的好:"计划永远赶不上变化",就算思考到业务的每个细节,也不可能设计出一套一劳永逸的结构出现,我们永远无法判断意外和明天哪个先来。因而,项目上线后,配备完善的监控警报系统也是必不可少的。不过值得注意的是:

监控系统的作用并不是用来提醒你项目"嗝屁"了的,而是用来提醒你:线上部署的应用系统可能会"嗝屁"或快"嗝屁"了,毕竟当项目灾难已经发生时再给警报,那到时候的情况就是:"亡羊补牢,为时已晚"。

通常情况下,在监控系统上面设置的性能阈值都会比最大极限值要低5~15%,如:最大极限值是85%,那设置告警值一般是75%左右就会告警,不会真达到85%才告警,只有这样做才能留有足够的时间让运维和开发人员介入排查。当系统发出可能"嗝屁"的警告时,开发和运维人员就应当立即排查相关的故障隐患,然后再通过不断的修改和优化,提前将可能会出现的性能瓶颈解决,这才是性能调优的正确方案。

因此,最终结论为:绝不能等到系统奔溃才去优化,预防胜于一切

1.4、无需追求完美,理性权衡利弊

"追求极致,做到完美"这点是大部分开发者的通病,很多人会因为这个思想导致自己在面临一些问题时束手无策,比如举个例子:

业务:MacBookPro一元购活动,预计访问压力:10000QPS

环境:单台机器只能承载2000QPS,目前机房中还剩余两台空闲服务器。

状况:此时就算将空闲的两台机器加上去,也无法顶住目前的访问压力。

此时你会怎么做?很多人都会茫然,这看起来好像是没办法的事情呀,似乎只能等死了.....

但事实真的如此吗?并非如此,其实这种情况也有多种解决方案,如:

  • ①停掉系统中部分非核心的业务,将服务器资源暂时让给该业务。
  • ②抛弃掉部分用户的请求,只接受处理部分用户的请求。
  • ③........

这些方案是不是可以解决上面的哪个问题呢?答案是肯定的。但完美主义者会认为:

系统中的服务不能停啊,得保持正常服务啊。

用户的请求怎么能抛,用户的访问必须得响应啊。

但事实告诉你的是:类似于京东、淘宝、12306等这些国内的顶级大厂,也照样是这么干的。好比阿里,在双十一的时候都会抽调很多冷门业务的服务器资源给淘宝使用,也包括你在参与这些电商平台的抢购或秒杀类活动时,你是否遇到过如下情况:

  • 服务器繁忙,请稍后重试......
  • 服务器已满,排队中.....
  • 前方拥堵,排队中,当前第x位.....

如果当你遇到了这些情况,答案显而易见,你的请求压根就没有到后端,在前端就给你pass了,然后给你返回了一个字符串,让你傻傻的等待。

这个例子要告诉大家的是:在处理棘手问题或优化性能时,无需刻意追求完美,理性权衡利弊后,适当的做出一些决断,抛弃掉一部分不重要的,起码比整个系统挂掉要好,何况之后照样也可以恢复。

1.5、性能调优的通核心步骤

性能优化永远是建立在性能瓶颈之上的,如果你的系统没有出现瓶颈,那则无需调优,调优之前需要牢记的一点是:不要为了调优而调优,而是需要调优时才调

而发现性能瓶颈的方式有两种,一种是你的应用中具备完善的监控系统,能够提前感知性能瓶颈的出现。另一种则是:应用中没有搭载监控系统,性能瓶颈已经发生,从而导致应用频繁宕机。大型的系统一般都会搭载完善的监控系统,但大多数中小型项目却不具备该条件,因此,大部分中小型项目发现性能瓶颈时,大多数情况下已经"嗝屁"了。

通常而言,性能优化的步骤可分为如下几步:

  • ①发现性能瓶颈:如有监控系统,那它会主动发出警报;如若没有,那出现瓶颈时应用肯定会出问题,如:无响应、响应缓慢、频繁宕机等。
  • ②排查瓶颈原因:排查瓶颈是由于故障问题导致的,还是真的存在性能瓶颈。
  • ③定位瓶颈位置:往往一个系统都会由多个层面协同工作,然后对外提供服务,当发现性能瓶颈时,应当确定瓶颈的范围,如:网络带宽瓶颈、Java应用瓶颈、数据库瓶颈等。
  • ④解决性能瓶颈:定位到具体的瓶颈后对症下药,从结构、配置、操作等方面出发,着手解决瓶颈问题。

本章则重点是阐述Java虚拟机-JVM相关的调优操作,但需要先提前说明的是:

单层面的性能调优其实只能当成锦上添花的作用,但绝对不能成为系统性能高/低、响应快/慢、吞吐量大/小的决定性要素。应用系统的性能本身就还算可以,那么调优的作用是让其性能更佳。但如若项目结构本身就存在问题,那么能够带来的性能提升也是有限的,如果你想让你的项目快到飞起,那么还需要从多个层面共同着手才能达到目的。

相关推荐
长天一色7 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
裴云飞1 天前
鸿蒙性能优化之布局优化
性能优化·harmonyos
kingapex12 天前
性能优化-数据库分区技术深入解析
数据库·oracle·性能优化·数据库设计
胡耀超2 天前
知识图谱入门——6:Cypher 查询语言高级组合用法(查询链式操作、复杂路径匹配、条件逻辑、动态模式创建,以及通过事务控制和性能优化处理大规模数据。
性能优化·知识图谱·cypher
EterNity_TiMe_3 天前
【Linux进程间通信】Linux匿名管道详解:构建进程间通信的隐形桥梁
linux·运维·redis·缓存·性能优化·学习方法
PangPiLoLo3 天前
高性能架构—存储高性能
java·数据库·redis·性能优化·架构
Hello Dam4 天前
【文件增量备份系统】MySQL百万量级数据量分页查询性能优化
java·mysql·性能优化·springboot·深分页优化
黑马金牌编程4 天前
nginx常用的性能优化
运维·服务器·nginx·性能优化
百年孤独_4 天前
对于基础汇编的趣味认识
汇编·性能优化