一、性能优化的流程
我们在对某个功能(或单个接口)做性能优化的时候。一般是该功能(或接口)性能无法满足我们的业务要求,所以被迫优化。在开始优化之前,我们需要明白一些理论知识。
1、常见的性能优化指标
1.1 响应时间
- 定义:接口从接收到请求到返回响应所需的时间。
- 常见优化方法:
- 缓存:减少对数据库或后端系统的频繁访问。
- 异步处理:将耗时操作放到后台,避免阻塞主线程。
- 数据压缩:减小返回的数据量,减少传输时间。
- 优化算法:使用高效的算法和数据结构
1.2 并发数
- 定义:接口能够同时处理的并发请求数量。
- 常见优化方法:
- 增加并发连接池:避免线程或数据库连接的瓶颈。
- 限流(Rate Limiting):通过限制并发请求数,保证系统稳定性。
- 适当配置线程池、数据库连接池等资源。
1.3 吞吐量
- 定义:单位时间内,接口可以处理的请求数量。通常以每秒请求数(RPS)来表示。
- 常见优化方法:
- 使用负载均衡:通过多个服务器分担请求压力。
- 数据库优化:通过分库分表、索引等方法提高数据库查询性能。
- 使用高效的缓存策略,减少对后端的访问。
- 异步化操作,提升并发处理能力。
1.4 资源消耗
- 定义:接口处理请求时消耗的系统资源(如 CPU、内存、I/O)。
- 常见优化方法:
- 优化数据处理流程,减少不必要的计算。
- 使用合适的缓存机制,避免重复计算。
- 监控和优化内存使用,防止内存泄漏。
- 使用合适的压缩算法,减少 I/O 操作。
这里有人可能会对RPS有点不大熟悉,那么它和QPS的区别是什么呢?
2.QPS和RPS的区别
2.1 QPS(Queries Per Second)
-
定义:QPS 表示系统每秒钟处理的查询次数,通常用于描述 数据库查询 或 搜索引擎 的查询频率。
-
使用场景:QPS 多用于衡量查询系统(如数据库、缓存系统、搜索引擎等)的处理能力,反映的是系统能够处理的查询请求的数量。
例如:对于一个数据库,QPS 可能表示每秒从数据库中查询的记录数。如果某个搜索引擎的 QPS 为 1000,意味着每秒钟有 1000 个查询请求提交到搜索引擎,它需要处理并返回相应的搜索结果。
2.2 RPS(Requests Per Second)
- 定义:RPS 表示系统每秒钟能够处理的请求次数。RPS 一般用于描述 Web 服务器 或 API 接口 的吞吐量,反映的是系统接受并处理请求的能力。
- 使用场景:RPS 多用于 Web 应用、API 服务、负载均衡器等,描述这些系统在处理 HTTP 请求时的能力。
例如:对于一个 Web 服务器,RPS 可能表示每秒处理的 HTTP 请求数量。如果某个 Web 服务器的 RPS 为 5000,意味着每秒钟该服务器可以处理 5000 个 HTTP 请求。
指标 | 区别 | 常用场景 |
---|---|---|
QPS | 每秒查询次数,常用于数据库或查询引擎 | 数据库、缓存、搜索引擎 |
RPS | 每秒请求次数,常用于衡量 Web 服务器或 API 的吞吐量 | Web 服务器、API 接口 |
实际上,QPS 是针对查询类操作的吞吐量度量,RPS 是针对网络请求的吞吐量度量。
3、性能优化怎么做
3.1 确定性能优化目标
在开始做性能优化之前,首先你要明确你的优化目标是什么,不同的优化目标对应着不同的优化策略,确定好性能优化目标,一方面可以避免过度优化 ,另一方面,是避免优化不足导致我们的系统无法继续支持后续发展。比如说,接口的QPS要达成多少,接口延迟需要达到多少,通过这些目标,才能明确我们的优化是否达标。
怎么算过度优化呢,比如说,一个内部系统页面查询太慢了,现在需要2s,然后你大费周章把它优化到了10ms , 花费了大量的时间和人力调整服务架构,搞分布式,提高了系统的复杂性,增大了系统维护成本,就没太大必要,因为它只是一个内部系统,我们在做任何决策的时候,都需要权衡我们的付出和收获,即ROI思想,把核心的资源用在核心的部分上
我们可以根据实际业务情况,来设定目标,例如搞活动的时候,我们要大概预估会有多少的活动用户,来确定某个查询接口的QPS,在评估上要有一定的系统冗余,即在保守估计上✖️个1.2~1.5倍,这样即使估摸不准确,或者突然有更多的参与用户,我们的系统也可以保证尽量不出问题。
确定性能目标,需要基于你手中拥有的资源和实际业务,做决策和权衡,比如说在保证接口时延不变的情况下,提高单机的QPS
说实话,决策和权衡能力很依赖一个人的经验,当然如果我们自己没有太多经验,那么我们可以去借鉴别人的经验。多实践多思考,必有收获。
3.2 找到瓶颈
为了更好的找到某个功能的瓶颈,我们可以对这个功能进行性能测试,单个请求测试和集群整体压测(全链路压测)。
单个请求测试的目的是确定我们自己的服务是否存在可优化点,比如接口延时是否符合预期,服务资源的使用是否合理,有没有异常报错
全链路压测的目的则是针对某个具体的功能进行上下游压测,方便我们找到整个链路中其他服务的卡点,来确定瓶颈是在本服务还是其他服务。
经常做活动服务的朋友应该对这方面比较有认知,活动对接口时延,QPS等要求都特别高,因此每次活动前都要模拟压测,确保活动当天不会出问题,一般都是进行全链路压测,通过链路追踪工具可以方便的看出具体是在哪个节点比较慢(本质上就是打日志分析),如果是本服务的瓶颈,我们可以针对性对具体的接口进行优化,如果是其他下游服务的瓶颈,则需要让其他团队帮忙优化。
当瓶颈点有多个时,我们可以找到最大的影响瓶颈,一步步做优化,为了控制好改造的成本,先解决一个相对好解决或者说是改造价值比较大的瓶颈,后续性能目标达不到再继续优化其他的点。
3.2 怎么对瓶颈进行优化
找到了瓶颈之后,我们就要分析瓶颈的原因是什么,不同的瓶颈分析的思路也是不同的。
如果是本服务的接口:在压测过程中,我们可以观察服务的资源消耗情况(内存、CPU、协程数),通过资源的使用情况来判断接口存在的问题。如果发现是内存的消耗过高导致单机吞吐量低,可以通过 pprof 抓取内存的火焰图,看具体是哪段代码在频繁的分配内存
而对于下游组件的瓶颈,例如说mysql。有一些常见的分析思路:
1、是否是服务调用 MySQL 的 IO 时间过长,这种情况可以看下是不是返回的数据量过大。
2、查看我们的 MySQL 请求是否存在慢查询,判断 SQL、索引使用合不合理。
3、如果排除了上述两个因素,需要分析下是否是因为我们的表太大、从库不够,还是说 MySQL 本身支持不了这么高的吞吐和这么严的延时要求
本质上观察几个点。
- 服务和mysql的连接是否正常(连接池、网络等)
- mysql 的响应是否正常(慢查询、查询量等)
针对Go来说。一个接口的瓶颈常见的问题有如下几点。
- 网络吞吐量
- Go服务资源限制
- 代码逻辑
- 下游组件的瓶颈
用人话说,首先可以先看看是不是代码问题,代码是不是有存在循环的数据库查询,可以改成批量查询来优化,同步操作能不能改成异步处理,内存使用是否合理,有没有内存泄漏,有没有协程泄漏,能不能加缓存层,网络调用是否有复用连接。
网络本身是否有问题,带宽,网络延迟。
服务的资源(内存、CPU、硬盘)够不够用
下游服务是不是高性能的组件。使用方式是否正确。
3.3 验证目标达成
为了验证性能目标是否达成,我们可以对调优过的应用进行性能测试,与优化前的各项指标进行对比,观测其是否符合预期。
如果瓶颈点没有消除或者性能指标不符合预期,则需要重复找瓶颈点、瓶颈分析、性能调优和验证目标是否达成的步骤,直到达成性能目标