如何优化你的 API 性能
本文作者 张聪毅
一、前言
一般来说,产品投入使用后,如果你的产品足够有吸引力,可以预知在未来一段时间,产品的用户将会越来越多,同时,这也对你产品的性能体验提出了更高的要求,要知道数据体量一旦上去了,不用惊讶,任何一个系统都开始或多或少会出现访问延。
说到这里,有人不禁要问,产品界面出现慢的时候跟数据量有啥关系?我明明看到的是 API 响应慢啊。说的没错,API 响应不够快是表象,要深背后的原因,且听我慢慢道来。
二、API 性能起源
说起性能这个事,不仅是针对服务端的 API。广义上来说,一切暴露给客户端操作的交互,都存在一个交互体验的过程,比如听到小 A 同学讲,这个 app 操作起来很流畅,又听到小 B 同学抱怨,什么系统,难用死了,这么卡好意思拿出来用...
但是真正在开始深入思考这件事情的时候,我们需要首先划分立场,主要分为下面几种:
系统终端用户
就是使用系统的人,这类人不懂技术,也不会给你太多时间听你解释为什么系统会变慢,他们要的是怎么在第一时间快速解决问题。
实施或技术客服
系统的前台技术支撑人员,他们能够快速了解终端用户反馈的问题,同时也能尽快将问题反馈给技术人员,属于连接着需求源端和技术终端的桥梁,一定情况下,他们也会利用常规的手段或经验协助处理系统卡顿的问题。
开发工程师
当问题上报到这一层时,表明需要通过开发者深入分析和定位问题的原因了,这个时候就需要技术手段的介入了。
为什么要这样划分呢?现实中,一个系统或 app 应用突然出现变慢的因素往往是很多原因造成,我们很难在第一时间拍脑袋说到底是什么原因引发的从而去界定责任人,而是需要通过不同渠道的人员层层递进,这样算是一个标准的做法。本篇将抛开其他因素,单从 API 这一层来深入聊聊性能这个事。
2.1 关于 API 性能的理论基础
下图展示了一个客户端操作 app 时,从发起一次请求到响应的完整过程
从这个图来看,我们站在不同的角度来分析性能这个事。
2.1.1 对于客户端
操作 app 前台页面时,一个请求发出去到 app 后台返回数据,然后页面渲染完成所经历的整个耗时过程,这个过程的长短可简单作为衡量本次操作性能的标准。
2.1.2 对于 API 接口
后台服务从收到这次请求开始算起,处理具体业务逻辑,最后将组装完成的数据响应给前台的整个耗时,这个耗时的长短决定了 API 性能的好坏。
2.1.3 对于 app 前台
页面发起请求,到接收到来自服务端的响应数据,再将数据完整的渲染到页面所花费的时间。
综上所述,用通俗的话来讲,性能这个事,以总结为下面这段话:
一次交互时,从发起一次请求开始,到后台响应数据,以及前台拿到数据并完成页面渲染的过程;
2.2 影响性能的关键因素
经验来讲,一次完整的 http 请求链路中,抛开其他的因素,影响性能几个核心因素可能有以下几点
- 网络带宽;
- API 接口耗时;
- 前台页面渲染速度;
2.2.1 网络带宽
这个太重要了,网络带宽属于支撑一个系统或 app 应用的基础设施,是系统维持稳定的关键因素,也是支撑前后台交互快速响应的基础。
2.2.2 API 耗时
在带宽不是问题之后,一个响应小于 200ms 的接口与超过 2 秒的接口,结果不言而喻,也就是说,如果你的 API 响应耗时不能缩短,不管前台技术多牛,交互体验上最差的一环还是会落在 API 这一层。
2.2.3 前台页面渲染速度
拿到 API 接口响应数据后,前台要将数据渲染并填充到页面上,抛开网络的因素,这个过程经历的时长可能与浏览器内核引擎技术,待展示的页面布局复杂度,以及前台自身的技术都有关系。
三、提升 API 性能的几个建议
在上面提到的对性能影响的几点因素中,其中可以说是非常核心的一个要素就是 API 的响应耗时。事实上,在很多互联网公司,在线上出现突发的性能问题时,一般都会第一时间通知服务端工程师,通过 API 出发,从而分析、定位和解决问题。经验来说,大多数情况下,最终的问题也确实落在 API 这一层,可见加强 API 自身的设计和优化是一项多么重要的工作。
接下来,小编以多年一线的开发和实践经验来聊聊如何对 API 的性能进行优化设计。
3.1 减少非必要的参数传递
下面是一段请求接口的部分代码
@GetMapping("/query/emp")
public Object queryEmpInfo(@HeaderParam(@RequestParam String userNo,
@RequestParam String tenantNo,
@RequestParamint type,
@RequestParamint String keyword,
@RequestParamint String mobile,
@RequestParamint int orderNo,
@RequestParamint String bizCode,
@RequestParamint String deptId,
@RequestParamint String groupId,
@RequestParamint String category
...) {
//TODO 业务逻辑
}
这是一段接近真实的线上代码,对于有密集恐惧症的同学来说,不禁会问,把这些参数封装一下不好吗?
3.1.1 问题分析
了解 http 请求的同学知道,get 请求是将 http 的 header 和 data 当作一条 Tcp 数据包发往服务端,本身承载数据量有限,依赖于 Tcp 负载能力,所以携带数据量很大的情况下,不仅服务器自身承载的压力大,数据到达服务端进行参数解析同样需要花费很长时间。
3.1.2 优化建议
基于上面的分析,从性能角度出发给出下面几点建议:
- GET 请求参数不要过多,一般限制在 5 个之内;
- 单个参数值不要过大,GET 请求对参数长度有限制;
- 长度过大的参数,可以考虑使用加密或者 hash,将参数值进行压缩处理;
3.2 尽量使用缓存
缓存的出现给很多读多写少的业务场景带来了架构设计上的便利和扩展性,合理利用缓存,可以充分的缓解业务高峰期服务端因为频繁查询数据库带来的压力,从而大大提升 API 接口的响应速度。但是引入缓存之后,也同时带来了编码的复杂性,一个比较常见的问题就是,如何保障数据库与缓存的一致性,这一点需要注意。
下图是服务端一个典型的缓存使用场景 API 逻辑流程图。
3.3 减少不必要的数据库查询
API 最终给到前台的数据往往来源很多,最终将各个来源的数据聚合好了之后再返回。为了聚合这些数据,往往需要多次查询数据库,甚至其他的数据存储等,我们知道与 mysql 的交互,IO 是很耗时的过程,所以在 API 逻辑编写过程中,非必要的情况下,尽量减少与诸如数据库等其他组件的交互频率。
3.4 增加数据表索引
索引的出现,就是为了更高效的检索数据,在很多数据存储引擎中都有索引的存在。API 经常涉及到与各种数据存储引擎打交道,比如 mysql,oracle,mongodb 等,这些存储引擎自身都提供了丰富而强大的索引结构。事实上,API 性能差在问题分析和定位之后,很大一部分是因为慢 SQL 或者数据量大造成的,基于这样的前提,可以给你的 API 查询中涉及到的表添加合适的索引,这将是一个很好的思路和突破点。
3.5 尝试使用流批处理
在不少业务场景下,涉及到大批量的数据需要入库的时候,可以考虑使用流批处理技术,将待入库的数据一次性组装好,分批或者一次性入库,尽量减少那种循环写库的操作。
3.6 使用异步
异步可以说是很多服务端开发者的福音,合理使用异步操作可以让你的 API 性能跃升到另一个高度。为什么这么说呢?因为异步意味着操作将不会占用主业务链路上的资源,另一方面,异步使得主业务的流程链路缩短,这是提升 API 性能的关键点。
下面用两张图说明同步和异步的处理过程,可以加深对异步处理的理解。这里的业务场景是,前台发起下单的请求,如果走同步逻辑,下单 API 中将会调用一系列其他的微服务接口并确认调用成功之后,才会响应数据给前台。如果是异步的逻辑,由于在某些情况下,用户的关注点在下单上,所以下单 API 不再等待其他微服务的响应,而是直接告知前台处理成功,后台将开启异步任务去处理其他业务。
3.6.1 同步请求处理流程
3.6.2 异步请求处理流程
四、写在文末
本文通过较长的篇幅,结合实践经验,总结了一些关于提升 API 性能的思考。一般来说,对于有经验的同学来说,在 API 设计之初,就应当尽可能的考虑一些常规的性能优化设计技巧,避免后续线上问题出现后带来的麻烦。