如何提升接口的性能

优化提升接口的性能

如何优化提升接口的性能问题,导致接口性能问题的原因千奇百怪,不同的项目不同的接口,原因可能也不一样。

本文总结了一些行之有效的,优化接口性能的办法。

优化索引

首先大家可能第一想到就是优化索引,没错,优化索引的成本是最小的。可以通过查看日志或监控平台自报告,查看某只接口用的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的成本是最小

串行该改并行

举个例子,有这个的业务场景:在用户信息查询接口中需要返回用户名称、性别、等级、头像、积分、成长值等信息。而用户名称、性别、等级、头像在用户服务中,积分在积分服务中,成长值在成长值服务中。为了汇总这些数据统一返回,需要另外提供一个对外接口服务。于是,用户信息查询接口需要调用用户查询接口、积分查询接口和成长值查询接口,然后汇总数据统一返回。

image-20221018093654268

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

并行调用

image-20221018093926199

数据缓存

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

image-20221018094210409

重复调用

重复调用在代码中随处可见,但如果没有控制好,会非常影响接口的性能。

在循环中调用查询数据库是非常不可取的,每查询一次数据库,一次就是远程调用。

数据结果集的大小要做限制,最好一次不要请求太多的数据。分页处理。

异步处理

接口性能优化,需要重新梳理一下业务逻辑,看看是否有涉及上不太合理的地方。

比如有个用户请求接口中,需要做业务操作,发站内通知,记录操作日志。为了实现起来比较方便,如果将这些逻辑放在接口中同步执行,势必会对接口性能造成一定的影响。

image-20221018094656762

遵循一个原则:核心逻辑可以同步执行,同步写库。非核心逻辑,可以异步执行,异步写库。

上面这个例子中,发站内通知和用户操作日志功能,对实时性能要求不高,即使晚点写库,用户无非是晚点收到站内通知,或者运营晚点看到用户操作日志,对业务影响不大,所以完全可以异步处理。

线程池

image-20221018095022972

发站内通知和用户操作日志功能,被提交到了两个单独的线程池中去执行,接口中重点关注的是业务操作,把其他的逻辑交给线程异步执行,让接口性能瞬间提升。

问题:使用线程池有个问题就是:如果服务器重启了,或者是需要被执行的功能出现异常了。无法重试,会丢数据。那怎么处理?可以使用中间件mq。

使用中间件mq

image-20221018095344231

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

避免大事务

image-20221018095542583

大事务的问题可能会造成接口超时,对接口的性能有直接的影响。

优化:

  • 将查询(select)方法放到事务外
  • 事务中避免远程调用
  • 事务中避免一次性处理太多数据
  • 有些功能可以非事务执行
  • 有些功能可以异步处理

锁粒度

某些业务场景中,为了防止多个线程并发修改某个共享数据,造成数据异常,通常情况下选择加锁处理,但如果锁加的不好,导致锁的粒度太粗,也会非常影响接口性能。

尽量将锁的粒度细化到只需要用到锁的地方。

分页处理

有时候调用某个接口批量查询数据,比如:通过用户id批量查询出用户信息。若一次查询的用户数量太多,元辰调用接口,会发现该用户查询接口经常超时。

分页处理,将一次获取所有的数据的请求,改成分多次获取,每次只获取一部分用户的数据,最后进行合并和汇总。

同步调用

...

异步调用

...

加缓存

解决接口性能问题,加缓存时一个非常高效的方法。但不能为了缓存而缓存,还是要看具体的业务场景。毕竟加了缓存,会导致接口的复杂度增加,它会带来数据不一致问题。

在有些并发量比较低的场景中,比如用户下单,可以不用加缓存。还有些场景,比如在商场首页显示商品分类的地方,假设这里的分类是调用接口获取到的数据,但页面暂时没有做静态化。如果查询分类树的接口没有使用缓存,而直接从数据库查询数据,性能会非常差。

redis缓存

在关系型数据库中,比如:mysql,级联菜单的查询是一个非常耗时的操作。这时候想要用缓存,可以用jedis和redisson框架直接从缓存中获取数据。

此外,我们还需要有个job每隔一段时间,从数据库中查询菜单数据,更新到redis中,这样以后每次杜能直接从redis中获取菜单的数据,从而无需访问数据库了。

二级缓存

上面的方案是基于redis缓存的,虽然说redis访问速度很快。但是毕竟是一个远程调用,而且菜单树的数据很多,在网络传输的过程中,是有些耗时的。有没有办法,不经过请求远程,就能直接获取到数据呢?使用二级缓存,即基于内存缓存。除了自己手写的内存缓存之后,目前使用比较多的内存缓存框架有:guava、ehcache、caffine等。

image-20221018101322718

该方案的性能更好,但是有个缺点就是,如果数据更新了,不能及时刷新缓存。此外。如果有多台服务器节点,可能存在各个节点上数据不一样的情况。

二级缓存给我们带来性能提升的同时,也带来了数据不一致的问题。使用二级缓存一定要结合实际的业务场景,并非所有的业务场景都使用。

接口按需返回

在定义接口的返回数据结构时,根据需求按需返回,比如只需要用户的姓名、工号、职务、部门数据,不需要直接返回一个实体对象包含所有字段,只返回所需要的数据才是最佳选择。前端在传递参数的时候,尽量根据需求传递参数,不要为了方便讲所有参数封装后传入到后端。

posted @ 2022-10-18 11:12  荀飞  阅读(84)  评论(0)    收藏  举报