如何提升接口的性能
优化提升接口的性能
如何优化提升接口的性能问题,导致接口性能问题的原因千奇百怪,不同的项目不同的接口,原因可能也不一样。
本文总结了一些行之有效的,优化接口性能的办法。
优化索引
首先大家可能第一想到就是优化索引,没错,优化索引的成本是最小的。可以通过查看日志或监控平台自报告,查看某只接口用的sql语句耗时比较长的。
1. 这条sql加了索引没
2. 加了索引生效没
3. mysql选错索引没
尽量使用主键,这样可以减少表的回表查询次数,所谓回表查询其实是因为mysql的数据结构决定:因为其主键的叶子节点下存储的是当条数据,但是其他索引也维护了一个树结构,其叶子节点下存储的是当条数据的主键值,然后会在通过这个主键id再回表一次去通过id查询数据。
没加索引
sql语句中where条件的关键字段,或者order by后面的排序字段,忘了加索引,这个问题在项目中很常见。项目刚开始的时候,由于表中的数据量小,加不加索引sql查询性能差距不大。
后来,随着业务的发展,表中数据量越来越多,就不得不加索引了。
-- 查看表的索引
show index from student;
-- 查看整张表的建表语句,也可以查看索引情况
show create table student;
索引没生效
通过上面的方式可以查询出是否建立了索引,但是它生效了没,如何判定索引是否生效呢,可以使用explain命令,查看mysql的执行计划,它会显示索引的使用情况。
-- explain检查索引使用情况
explain select * from student where name='xxx';
- id:select唯一标识
- select_type:select类型
- table:表名称
- partitions:匹配的分区
- type:连接类型
- possible_keys:可能得索引选择
- key:实际用到的索引
- key_len:实际索引长度
- ref:与索引比较的列
- rows:预计要检查的行数
- filtered:按表条件过滤的行百分比
- extra:附加信息
sql语句没有走索引,排除没有建索引之外,最大的可能性是索引失效,那索引失效的原因是哪些呢?
索引失效的常见原因
- 不满足最左前缀原则
- 返回索引列没有放到最后
- 使用了
select *
- 索引列上有计算
- 索引列上使用了函数
- 字符类型没加引号
- 用is null和 is not null 没注意字段是否允许为空
- like查询左边有
%
- 使用
or
关键字时没有注意
选错索引
有没有遇到过这样一种情况:明明是同一条sql,只有入参不同而已。有的时候走的索引a,有时候却走的索引b,这就是mysql会选错索引,必要时可以使用force index
来强制sql走某个索引。
优化sql语句
优化索引后没啥效果,那我们就可以考虑优化sql语句,相对于改造代码,优化sql的成本是最小
串行该改并行
举个例子,有这个的业务场景:在用户信息查询接口中需要返回用户名称、性别、等级、头像、积分、成长值等信息。而用户名称、性别、等级、头像在用户服务中,积分在积分服务中,成长值在成长值服务中。为了汇总这些数据统一返回,需要另外提供一个对外接口服务。于是,用户信息查询接口需要调用用户查询接口、积分查询接口和成长值查询接口,然后汇总数据统一返回。

这种串行调用远程接口性能是非常不好的,调用远程接口的总耗时为所有的远程接口耗时之和。那如何优化远程接口性能呢?
并行调用

数据缓存
上面说到的用户信息查询接口需要调用用户查询接口、积分查询接口和成长值查询接口,然后汇总数据统一返回。那么,可以把用户信息、积分和成长值的数据统一存储到一个地方,比如:redis,存的数据结构就是用户信息查询接口所需要的内容。然后通过用户id,直接从redis中查询数据出来。

重复调用
重复调用在代码中随处可见,但如果没有控制好,会非常影响接口的性能。
在循环中调用查询数据库是非常不可取的,每查询一次数据库,一次就是远程调用。
数据结果集的大小要做限制,最好一次不要请求太多的数据。分页处理。
异步处理
接口性能优化,需要重新梳理一下业务逻辑,看看是否有涉及上不太合理的地方。
比如有个用户请求接口中,需要做业务操作,发站内通知,记录操作日志。为了实现起来比较方便,如果将这些逻辑放在接口中同步执行,势必会对接口性能造成一定的影响。

遵循一个原则:核心逻辑可以同步执行,同步写库。非核心逻辑,可以异步执行,异步写库。
上面这个例子中,发站内通知和用户操作日志功能,对实时性能要求不高,即使晚点写库,用户无非是晚点收到站内通知,或者运营晚点看到用户操作日志,对业务影响不大,所以完全可以异步处理。
线程池

发站内通知和用户操作日志功能,被提交到了两个单独的线程池中去执行,接口中重点关注的是业务操作,把其他的逻辑交给线程异步执行,让接口性能瞬间提升。
问题:使用线程池有个问题就是:如果服务器重启了,或者是需要被执行的功能出现异常了。无法重试,会丢数据。那怎么处理?可以使用中间件mq。
使用中间件mq

对于发站内通知和用户操作日志的功能,在接口中并没真正的实现,它只发送了mq消息到mq服务器。然后由mq消费者消费消息时,才真正的执行了这两个功能。
避免大事务

大事务的问题可能会造成接口超时,对接口的性能有直接的影响。
优化:
- 将查询(select)方法放到事务外
- 事务中避免远程调用
- 事务中避免一次性处理太多数据
- 有些功能可以非事务执行
- 有些功能可以异步处理
锁粒度
某些业务场景中,为了防止多个线程并发修改某个共享数据,造成数据异常,通常情况下选择加锁处理,但如果锁加的不好,导致锁的粒度太粗,也会非常影响接口性能。
尽量将锁的粒度细化到只需要用到锁的地方。
分页处理
有时候调用某个接口批量查询数据,比如:通过用户id批量查询出用户信息。若一次查询的用户数量太多,元辰调用接口,会发现该用户查询接口经常超时。
分页处理,将一次获取所有的数据的请求,改成分多次获取,每次只获取一部分用户的数据,最后进行合并和汇总。
同步调用
...
异步调用
...
加缓存
解决接口性能问题,加缓存
时一个非常高效的方法。但不能为了缓存而缓存,还是要看具体的业务场景。毕竟加了缓存,会导致接口的复杂度增加,它会带来数据不一致问题。
在有些并发量比较低的场景中,比如用户下单,可以不用加缓存。还有些场景,比如在商场首页显示商品分类的地方,假设这里的分类是调用接口获取到的数据,但页面暂时没有做静态化。如果查询分类树的接口没有使用缓存,而直接从数据库查询数据,性能会非常差。
redis缓存
在关系型数据库中,比如:mysql,级联菜单的查询是一个非常耗时的操作。这时候想要用缓存,可以用jedis和redisson框架直接从缓存中获取数据。
此外,我们还需要有个job每隔一段时间,从数据库中查询菜单数据,更新到redis中,这样以后每次杜能直接从redis中获取菜单的数据,从而无需访问数据库了。
二级缓存
上面的方案是基于redis缓存的,虽然说redis访问速度很快。但是毕竟是一个远程调用,而且菜单树的数据很多,在网络传输的过程中,是有些耗时的。有没有办法,不经过请求远程,就能直接获取到数据呢?使用二级缓存
,即基于内存缓存。除了自己手写的内存缓存之后,目前使用比较多的内存缓存框架有:guava、ehcache、caffine等。
该方案的性能更好,但是有个缺点就是,如果数据更新了,不能及时刷新缓存。此外。如果有多台服务器节点,可能存在各个节点上数据不一样的情况。
二级缓存给我们带来性能提升的同时,也带来了数据不一致的问题。使用二级缓存一定要结合实际的业务场景,并非所有的业务场景都使用。
接口按需返回
在定义接口的返回数据结构时,根据需求按需返回,比如只需要用户的姓名、工号、职务、部门数据,不需要直接返回一个实体对象包含所有字段,只返回所需要的数据才是最佳选择。前端在传递参数的时候,尽量根据需求传递参数,不要为了方便讲所有参数封装后传入到后端。