一个应用必须完成预期的多种需求,主要包括功能性需求(即应该做什么,比如各种存储、检索、搜索和处理数据)和一些非功能性需求(如性能、安全、可靠性、合规性、可伸缩性、兼容性、可维护性,等)。本章我们着重梳理讨论下性能。
性能概述
性能(Performance)是系统和软件在规定的时间和吞吐量参数内执行其功能的能力,以及在规定条件下有效利用资源(如CPU、内存、存储和网络设备)的能力。高性能的系统或软件,在以下方面表现出极大的优势:
(1) 提升用户体验。高性能的系统或软件能够快速响应用户请求,减少等待时间,提高用户满意度。
(2) 优化资源利用与降低成本。性能优化能减少CPU、内存、网络等资源消耗,高性能的系统或软件能最大化利用现有的硬件资源,完成更多工作,从而在同等资源情况下实现更高的业务处理能力,进而降低运行成本。与此相反,糟糕的性能往往需要"堆硬件"来弥补,成本呈指数级上升。
(3) 保证系统稳定性。高性能的系统或软件不会牺牲系统稳定性,相反会提高性能的稳定性。当出现实际吞吐量或访问量远大于预期设计时,高性能的系统或软件的抗压能力更强,更能减少宕机的风险,更好的支撑业务增长。许多系统故障(崩溃、无响应)的根源在于性能问题(如内存泄漏、线程阻塞、死锁、资源耗尽)。性能优化和测试有助于提前发现和解决这些隐患,使系统更健壮,减少宕机风险。
(4) 提升竞争力。在功能相似的产品中,性能优越的产品(启动更快、操作更流畅、响应更迅速、承载用户更多)往往能赢得更多用户青睐,形成关键竞争优势。
性能定义
在ISO/IEC-25019:2023标准中,对性能进行了如下定义:性能(Performance)是系统和软件在规定的时间和吞吐量参数内执行其功能的能力,以及在规定条件下有效利用资源的能力:资源可以是CPU、内存、存储和网络设备。注意:
(1) 资源的范围很广。资源可包括其他软件产品、系统的软硬件配置、能源和材料(如打印纸、存储介质)。
(2) 性能的评估是在一个有限环境下的能力评估。不能再不考虑资源或时间的情况下,进行性能评估。
(3) 性能优化的前提是确保系统或软件的稳定性、可靠性,不应牺牲稳定性、可靠性,来追求性能。
性能组成
在ISO/IEC 25019:2023标准中,性能由时间特性(time behaviour)、资源利用率(resource utilization)、容量(capacity)组成。
(1) 时间特性(time behaviour)。产品在特定条件下执行特定功能的能力,使响应时间和吞吐率满足要求。
(2) 资源利用性(resource utilization)。产品在特定条件下执行特定功能所使用的资源不超过特定数量的能力。
(3) 容量(capacity)。产品满足产品参数最大限制要求的能力:参数可包括可存储的项目数、并发用户数、通信带宽、事务吞吐量和数据库大小等。
性能度量
性能度量前提
性能度量的核心前提是 目标清晰、指标明确、环境可控、真实工作负载、工具可靠、基准和稳态。只有满足这些条件,度量结果才能有效指导优化决策(如代码重构、索引优化或硬件扩容)。性能度量(Performance Measurement)绝非简单的"运行测试并记录数字"过程。在开始任何性能测试或监控之前,必须明确并满足一系列关键前提条件。 忽略这些前提,得到的度量结果很可能无效、不可靠、无法比较,甚至误导决策。
(1) 目标清晰。性能度量必须服务于具体的、可衡量的目标。如业务目标(如提升用户转化率、减少服务器成本)、性能目标(用户关键操作响应时间 < 1秒 (P9999)、系统在高峰负载1000 TPS下,CPU使用率<70%、批量作业处理100万条记录时间<30分钟)。
(2) 指标明确。明确性能目标后,接下来就是选择与目标直接相关的、可量化的指标(如响应时间、吞吐量、资源利用率、错误率等)。
(3) 环境可控。建立一致且可控的测试/运行环境。环境是性能结果的根基,必须可控且可重现,如:
硬件规格: 服务器型号、CPU(型号、核心数、频率)、内存(容量、速度、通道)、存储(类型-SSD/HDD、型号、RAID、文件系统)、网络(带宽、延迟、交换机);
软件配置: 操作系统(版本、内核参数)、中间件(Web服务器、应用服务器、数据库、缓存、消息队列的版本、配置参数)、依赖库/框架版本、应用程序版本及配置;
网络环境: 测试客户端与服务器的网络拓扑、带宽限制、延迟模拟(如果需要模拟公网或特定网络条件)。
数据环境: 数据库的数据量、分布、索引状态。测试数据应能代表生产环境的规模和分布特点。
(4) 真实工作负载。设计与实施真实的工作负载。 负载是驱动性能表现的引擎,必须真实反映生产场景。选择合适的工具(如 JMeter, k6, Gatling, Locust, LoadRunner)来精确生成和控制定义的负载。不真实的负载会导致度量结果无法反映生产环境的真实性能。负载设计是性能测试成功的关键挑战之一。
(5) 工具可靠。确保度量工具本身的准确性和低开销。工具自身要可靠且不影响被测系统。使用经过验证的、可靠的性能监控和测试工具(如 Prometheus/Grafana, ELK Stack, Dynatrace, AppDynamics, JMeter, Profilers)。评估监控代理或测试工具本身对系统资源(CPU、内存、网络、磁盘)的消耗。过高的工具开销会扭曲被测系统的真实性能("观察者效应")。尽量使用低开销的工具和配置。工具不准确或自身消耗过大,会污染度量数据,导致结论错误。
(6) 基准和稳态。定义基准和建立稳态。
基准:需要有一个已知的、稳定的状态作为比较的起点(例如,系统上线时的性能、上一个版本的性能、修复某个问题后的性能)。这个基准需要在满足前述前提(相同环境、相同负载定义等)下获得。
系统预热: 许多系统(如JVM应用、数据库缓存)在启动后需要一段时间达到性能稳定状态。度量应该在系统完成预热并进入稳态(资源利用率和性能指标相对稳定)后才开始记录。忽略预热阶段的数据可能导致结果偏高(初始冷状态慢)或偏低(缓存未命中率高)。
性能度量指标
性能度量指标是评估系统或软件在性能具体方面(执行速度、执行效率、容量、资源消耗、稳定性等)的具体量化标准。性能度量的指标没有统一的标准,不同的度量目标,需要关注的度量指标各有不同。如对用户来说,关注响应时间(Response Time)、延迟(Latency);对系统能来说,关注QPS(Queries Per Second,每秒请求数)、TPS(Transactions Per Second,每秒事务量)、吞吐量(Throughput);对资源消耗来说,关注CPU 利用率、内存利用率、磁盘 I/O、网络 I/O;对容量来说,关注并发用户数/连接数、错误率、成功率。
这里仅对QPS/RPS、Throughput、资源利用率、并发数、SLA等常见指标进行说明,其他性能度量指标还请自行学习。
Throughput(吞吐量)
Throughput(吞吐量)是衡量系统或软件处理能力的关键指标,表示单位时间内系统成功处理的有效工作量。其量化公式如下:
Throughput = (Number of Successful Requests) / (Time Period)
其中:
- Throughput:吞吐量,表示单位时间内系统成功处理的有效工作量。
- Number of Successful Requests:在指定时间周期内,系统成功处理(即返回正确结果且无错误)的请求数量。
- Time Period:度量吞吐量的时间窗口,常用单位如秒(s)、分钟(min)、小时(h)等。
该公式是衡量请求处理类系统吞吐量的通用形式。在实际应用中,可根据具体场景替换"请求"为其他有效工作单元,例如:
- 数据库场景:Throughput = (Number of Successful Transactions) / Time Period (即 TPS)
- 磁盘 I/O 场景:Throughput = (Data Volume Transferred in Bytes) / Time Period (即 MB/s 或 GB/s)
- 网络场景:Throughput = (Data Volume Transferred in Bits) / Time Period (即 Mbps 或 Gbps)
不同场景下,Throughput的衡量方式和优化目标各不相同。对于请求来说,QPS/RPS就是Throughput;对于数据库来说,TPS就是Throughput;对于磁盘来说,MB/s 或 GB/s就是 Throughput;对于网络来说,Mbps/Gbps就是Throughput。
QPS(Queries Per Second)、RPS(Requests Per Second)、RPS(Response Per Second)
QPS(Queries Per Second),即"每秒查询数",用来衡量一个系统(通常是数据库、搜索引擎、缓存系统等)每秒能够处理的查询(Query)请求数量。
RPS(Requests Per Second),即"每秒请求数",用来衡量一个系统(如Web服务、API接口、网关、负载均衡器等)每秒钟能够接收并处理的请求(Request)数量。
RPS(Response Per Second),直译为"每秒响应数"。Response Per Second是一个误区,实际并没有这种性能度量指标。
资源利用率
资源利用率(Resource Utilization Rate)是衡量系统各类计算资源(如CPU、内存、磁盘、网络等)在单位时间内被实际使用的比例。通过资源利用率的监控和分析,可以有效发现系统瓶颈、优化资源配置和提升整体处理能力。资源的类型有很多,如CPU、内存、磁盘、网络带宽等。
(1) CPU利用率(CPU Utilization),指CPU实际用于处理任务的时间占总运行时间的百分比。
(2) 内存利用率(Memory Utilization),指实际被系统或应用程序占用的内存与总可用内存的比例。
(3) 磁盘利用率(Disk Utilization),包含磁盘空间利用率和磁盘I/O利用率。
空间利用率:已用磁盘空间与总空间的比值。
磁盘I/O利用率:磁盘读/写操作时间占比或速率。
(4) 网络带宽利用率(Network Utilization),指网络上传输的数据量与带宽上限的比值。
并发数
并发数(Number of Concurrency / Concurrency Level),指系统、应用或服务在同一时刻能够同时处理的请求、任务、连接或用户数。 并发数是衡量系统"同时处理能力"的核心参数,反映在某一瞬间,系统能并行执行、响应多少个操作或服务多少个用户。例如,在一个Web服务中,并发数可以表示当前有多少个HTTP请求正在被服务器同时处理;在数据库中,可以表示同时发起并执行的查询数量;在网络层面,表示连接中的TCP/HTTP连接数量,等。
SLA
SLA(Service Level Agreement,服务水平协议)是服务提供方与客户(或内部使用方)之间达成的关于服务可获得性、性能、响应时间、可用性、支持级别等具体标准和保障的书面协议或合同。SLA明确描述了服务质量的度量标准、目标值、评价周期以及违反协议时的责任或赔偿机制,是现代服务行业和IT运维的重要基础。
SLA 的本质是将模糊的"服务承诺"转化为可测量、可执行、可追责的技术契约,包含三大核心层:
- SLI(Service Level Indicator,服务等级指标),量度服务质量的基础指标(如延迟、错误率、可用性)。示例:API 的 P99 响应时间 ≤ 200ms
订单支付成功率的错误率 < 0.1% - SLO(Service Level Objective,服务等级目标),SLI 必须达成的具体数值目标及时间范围。
关键公式:SLO = SLI 目标值 + 统计周期 + 覆盖范围 - 处罚机制(Consequence),触发条件:SLO 未达标时的经济赔偿或服务补偿。行业惯例:
可用性 赔偿方案
99.9% → 99.5% 返还月费 10%
< 99.5% 返还月费 30%
性能测试方法
性能测试是评估系统在特定负载下的响应能力、稳定性和资源消耗等特征的关键手段。以下是4种常用的测试方法。
(1) 基准测试。在受控的、标准的环境下,对单一、特定的、小规模的负载(通常是单用户或极低并发)运行测试,测量 系统执行特定操作(如单个API调用、单个页面加载、单个SQL查询)所需的时间或其他资源消耗。基准测试是衡量系统在标准环境或基础负载下的性能,获取参考基线数据,便于和业界标准或历史数据对比。
(2) 负载测试。模拟真实世界中可能遇到的典型用户行为和数量级负载,主要用来评估系统在预期正常或峰值业务负载下的表现。验证系统是否能满足性能需求规格(如"在1000用户并发登录时,平均响应时间<2秒,错误率<0.1%")。
(3) 压力测试。"施加超出预期能力的压力,观察系统如何失效和恢复"。不是为了证明系统能工作,而是为了了解它失效的边界和方式。主要用来评估系统在 极端、超负荷 条件下的表现和稳定性。目标是找出系统崩溃点、瓶颈点以及在过载情况下的恢复能力。注意:"压测"一词在日常口语中常被泛化使用,有时甚至指代负载测试。但在专业语境下,它特指这种"施加超负荷压力"的测试。
(4) 并发测试。主要用来评估系统在大量用户/线程/进程同时执行相同或不同操作时的正确性和性能表现。特别关注可能产生资源争用或竞争条件的关键操作(如库存扣减、余额更新)。主要用来:验证系统在高并发操作下的功能正确性(无数据损坏、业务逻辑错误);检测和定位资源争用(如数据库锁、线程锁)问题;评估并发控制的效率。
性能优化编程规范
当考虑到性能优化时,有一套已经被接受的好的编程实践,它们的应用相当普遍,而且可以减少交付系统中的故障。这些编程实践是:
(1) 准则1:选择合适的数据结构,降低算法的时间复杂度;
(2) 准则2:避免重复创建对象;
(3) 准则3:使用批处理能力;
(4) 准则4:优化数据库性能;
(5) 准则5:减少网络交互次数;
(6) 准则6:合理使用多线程;
(7) 准则7:避免锁竞争和死锁;
(8) 准则8:简化设计,避免过度设计;
这些准则可以应用在任何一个系统开发使用的语言中,尽管它们所使用的方式取决于系统开发所使用的特定语言和符号系统。遵循这些准则还可以降低程序中引入性能瓶颈的可能性。
准则1:选择合适的数据结构,降低算法的时间复杂度
数据结构决定了数据存储和操作的效率。不同数据结构适用于不同的场景,选择错误会导致不必要的性能损耗。算法复杂度越低,程序运行速度越快,资源消耗越少。特别是在数据量较大时,提升明显。一般情况下,推荐的时间复杂度是O(1)、O(n)、O(log n),可以接收的时间复杂度O(n²)、对于大于O(n²)的复杂度,要谨慎。
准则2:避免重复创建对象
重复创建对象,会引入以下问题:
(1) 频繁创建对象会增加内存分配与垃圾回收压力,影响系统响应速度,甚至导致内存泄漏或崩溃。
(2) 对象初始化耗时,特别是数据库连接、文件IO、复杂模型等,反复创建与销毁非常低效。
(3) 某些资源对象不能重复创建,如网络连接、线程等,重复创建和销毁可能导致资源浪费或异常。
为避免重复创建对象,可以采用以下策略:
(1) 对象复用。用成员变量存储、静态变量共享、池化技术。
(2) 对象池技术。限制对象数,实例化后反复借用,典型如数据库连接池、线程池。如Java的ThreadPoolExecutor、Spring的数据库连接池(HikariCP、Druid)。
(3) 单例模式。某些全局配置、工具对象,仅需创建一次并且在全局复用。
(4) 缓存已创建的对象。对于耗时操作结果可放缓存,下次直接复用。如常用的图片资源、算法结果等。
准则3:使用批处理能力
批处理能力指的是将多个操作或任务一次性集中执行,而不是逐条(或逐个)独立完成。这样可以减少调用次数、网络传输、IO、数据库事务、资源竞争等带来的开销,提升执行效率。
在使用批处理的时候,也有一些误区要注意:
- 永远不要为了批处理而牺牲业务正确性
优先保证数据一致性,再优化性能 - 批处理大小 = f(资源, 延迟, 可靠性)
通过A/B测试确定最优值(不要拍脑袋!) - 监控比实现更重要
没有监控的批处理等于埋下定时炸弹 - 批处理不是银弹,但不用就是自残
I/O密集型系统中,批处理是性能的生命线 - 考虑失败重试策略
批量操作出错时需妥善回滚、重试或拆分处理。
准则4:优化数据库性能
数据库性能优化是应用系统性能提升的核心环节之一,既有能力能让响应速度更快,也能支撑更多用户并发,为业务扩展和稳定性打下基础。数据库是系统的核心瓶颈高发区--一个设计不良的查询可能让CPU飙升至100%,一次错误的索引会导致服务雪崩。
数据库性能优化是一项系统工程,横跨结构设计、SQL编写、服务器配置、应用协作、日常维护等全过程。建议在架构/开发/运维每个环节持续关注,快速定位与解决效率低下的问题,保障核心业务的数据层性能和稳定性。
准则5:减少网络交互次数
网络交互是分布式系统中最大的性能瓶颈之一。通过减少网络请求次数,可以显著降低延迟、提高吞吐量并减少资源消耗。典型的优化方法有:
1 接口/服务整合
将相关操作合并为一个接口,一次性传输/处理多个数据。如前端请求合并:将多个API请求整合为一个批处理请求,返回多块数据。微服务之间的"聚合服务"或"网关"设计,一条路由处理多项需求。
2 数据批量处理。
提交/获取多条数据,减少重复的单条请求。如:批量插入/查询(数据库、搜索接口、消息队列)、文件多行/分块处理、
一次请求传递多个ID,一次返回多个结果。
- 本地/应用缓存机制
利用前端、业务中台、后端甚至多级缓存,减少对远程服务的重复访问。如:热点数据本地缓存,下次直接读取;业务缓存(Redis、Memcached)。
-
数据压缩与协议优化。如数据压缩,提高单次传输带宽利用率,减少总数据包数;尽量采用更高效的二进制协议(如gRPC、Thrift、Protobuf等);减少无用字段、精简响应内容。
-
异步合并与预取。如将用户操作批量收集,批量发送(如日志、埋点、消息推送等);用户进入页面时预取相关数据,避免多次请求。
-
长连接与推送。如websocket、长轮询等模式维护连接,减少连接建立/断开次数,在同一连接上传输批量数据;
实时推送,替代频繁的轮询请求。
准则6:合理使用多线程
合理使用多线程是现代软件系统并发能力和性能提升的重要手段。多线程可以充分利用多核CPU,提升程序处理速度和吞吐率,但不合理使用则会带来线程安全、死锁、资源竞争等问题,并可能降低系统稳定性。因此,掌握多线程的合理用法非常重要。
多线程是双刃剑------正确使用可使吞吐量提升10倍,滥用则导致死锁、CPU飙升、响应时间波动。80%的生产事故源于不合理的多线程设计,而非线程本身。
准则7:避免锁竞争和死锁
避免锁竞争和死锁是多线程/多进程并发编程中的关键性能与稳定性要求。锁竞争会降低程序效率,死锁则导致程序永久挂起,严重影响系统可用性。
● 锁竞争:多个线程同时争夺同一资源锁,导致线程阻塞、效率下降。竞争越激烈,系统吞吐越低。
● 死锁:多个线程互相持有对方需要的锁而都无法释放,导致所有相关线程都永远等待,程序无法继续运行。
准则8:简化设计,避免过度设计
简化设计,避免过度设计 是高效、高性能、高可维护性软件开发的重要原则之一。合理的系统设计能帮助团队聚焦核心需求、快速反馈和迭代,减少不必要的复杂度和性能负担。
过度设计(Over Engineering)是指在软件开发时,为了应对"可能"的变化、扩展和极端场景,在现有需求尚未出现时,为系统加入过多"通用性""可扩展性""灵活性"甚至冗余的结构、模式和机制,导致系统架构和代码复杂度急剧上升。
简化设计的核心原则
- "You Aren't Gonna Need It"(YAGNI)原则
不要为尚未出现的需求设计和实现功能,只做当前明确需要的内容。 - KISS(Keep It Simple, Stupid)原则
让事情保持简单,不要让系统结构、代码、配置等复杂化。 - 避免过度抽象和层层封装
能直观实现的代码不搞多余的工厂、桥接、模板模式等;接口和抽象只为后续真正有不变性/多实现的地方服务。 - 灵活与简单的平衡
可扩展点很有必要时才设计,且要文档说明其合理性;其余场景优先选直接而清晰的方案。 - 用现有成熟组件,不重复轮子
避免为特殊场景自行开发架构、工具,当主流框架即可满足80%以上需求时直接采用。
参考
https://courage007.blog.csdn.net/article/details/145972296 系统或软件的可靠性(Reliability)