一分快三-聊聊接口性能优化的11个小妙技

让建站和SEO变得简单

让不懂建站的用户快速建站,让会建站的提高建站效率!

你的位置:一分快三 > 一分快三官网 > 聊聊接口性能优化的11个小妙技
聊聊接口性能优化的11个小妙技
发布日期:2022-03-13 19:32     点击次数:121
弁言

接口性能优化关于从过后端建立的同学来说,笃定再熟谙不外了,因为它是一个跟建立讲话无关的大众问题。

该问题说简便也简便,说复杂也复杂。

有时分,只需加个索引就能惩处问题。 有时分,需要做代码重构。 有时分,需要加多缓存。 有时分,需要引入一些中间件,比如mq。 有时分,需要需要分库分表。 有时分,需要拆分行状。 等等。。。

导致接口性能问题的原因千奇百怪,不同的口头不同的接口,原因可能也不一样。

本文我回来了一些行之有用的,优化接口性能的见地,给有需要的至交一个参考。

1.索引

接口性能优化人人第一个料想的可能是:优化索引。

没错,优化索引的资本是最小的。

你通过搜检线上日记或者监控弘扬,查到某个接口用到的某条sql语句耗时比拟长。

这时你可能会有底下这些疑问:

该sql语句加索引了没?

加的索引收效了没?

mysql选错索引了没?

1.1 没加索引

sql语句中where条目的关键字段,或者order by背面的排序字段,忘了加索引,这个问题在口头中很常见。

口头刚运转的时分,由于表中的数据量小,加不加索引sql查询性能区分不大。

其后,跟着业务的发展,表中数据量越来越多,就不得不加索引了。

不错通过号召:

show index from `order`; 

能单独搜检某张表的索引情况。

也不错通过号召:

show create table `order`; 

搜检整张表的建表语句,内部雷同会露馅索引情况。

通过ALTER TABLE号召不错添加索引:

ALTER TABLE `order` ADD INDEX idx_name (name); 

也不错通过CREATE INDEX号召添加索引:

CREATE INDEX idx_name ON `order` (name); 

不外这里有一个需要扎眼的方位是:想通过号召修改索引,是不行的。

当今在mysql中若是想要修改索引,只可先删除索引,再再行添加新的。

删除索引不错用DROP INDEX号召:

ALTER TABLE `order` DROP INDEX idx_name; 

用DROP INDEX号召也行:

DROP INDEX idx_name ON `order`; 
1.2 索引没收效

通过上头的号召咱们还是能够阐明索引是有的,但它收效了没?此时你内心偶然会冒出这么一个疑问。

那么,如何搜检索引有莫得收效呢?

答:不错使用explain号召,搜检mysql的试验考虑,它会露馅索引的使用情况。

举例:

explain select * from `order` where code='002'; 

赶走:

通过这几列不错判断索引使用情况,试验考虑包含列的含义如下图所示:

若是你想进一步了解explain的详备用法,不错望望我的另一篇著述《explain | 索引优化的这把绝世好剑,你果然会用吗?》

说真话,sql语句莫得走索引,摒除莫得建索引以外,最大的可能性是索引失效了。

底下说说索引失效的常饶恕因:

若是不是上头的这些原因,则需要再进一步排查一下其他原因。

1.3 选错索引

此外,你有莫得际遇过这么一种情况:明明是兼并条sql,惟有入参不同汉典。有的时分走的索引a,有的时分却走的索引b?

没错,有时分mysql会选错索引。

必要时不错使用force index来强制查询sql走某个索引。

至于为什么mysql会选错索引,背面有挑升的著述先容的,这里先留点悬念。

2. sql优化

若是优化了索引之后,也没啥后果。

接下来试着优化一下sql语句,因为它的改形资本相干于java代码来说也要小得多。

底下给人人列举了sql优化的15个小妙技:

由于这些妙技在我之前的著述中还是详备先容过了,在这里我就不真切了。

更详备的内容,不错看我的另一篇著述《聊聊sql优化的15个小妙技》,确信看完你会有好多成绩。

3. 资料调用

好多时分,咱们需要在某个接口中,调用其他行状的接口。

比如有这么的业务场景:

在用户信息查询接口中需要复返:用户称号、性别、等第、头像、积分、成长值等信息。

而用户称号、性别、等第、头像在用户行状中,积分在积分行状中,成长值在成长值行状中。为了汇总这些数据长入复返,需要另外提供一个对外接口行状。

于是,用户信息查询接口需要调用用户查询接口、积分查询接口 和 成长值查询接口,然后汇总额据长入复返。

调用经过如下图所示:

调用资料接口总耗时 530ms = 200ms + 150ms + 180ms

彰着这种串行调用资料接口性能诟谇常不好的,调用资料接口总的耗时为所有这个词的资料接口耗时之和。

那么如何优化资料接口性能呢?

3.1 并行调用

上头说到,既然串行调用多个资料接口性能很差,为什么不改成并行呢?

如下图所示:

调用资料接口总耗时 200ms = 200ms(即耗时最长的那次资料接口调用)

在java8之前不错通过罢了Callable接口,赢得线程复返赶走。

java8以后通过CompleteFuture类罢了该功能。咱们这里以CompleteFuture为例:

public UserInfo getUserInfo(Long id) throws InterruptedException, ExecutionException {     final UserInfo userInfo = new UserInfo();     CompletableFuture userFuture = CompletableFuture.supplyAsync(() -> {         getRemoteUserAndFill(id, userInfo);         return Boolean.TRUE;     }, executor);      CompletableFuture bonusFuture = CompletableFuture.supplyAsync(() -> {         getRemoteBonusAndFill(id, userInfo);         return Boolean.TRUE;     }, executor);      CompletableFuture growthFuture = CompletableFuture.supplyAsync(() -> {         getRemoteGrowthAndFill(id, userInfo);         return Boolean.TRUE;     }, executor);     CompletableFuture.allOf(userFuture, bonusFuture, growthFuture).join();      userFuture.get();     bonusFuture.get();     growthFuture.get();      return userInfo; } 

温馨提示一下,这两种方式别忘了使用线程池。示例中我用到了executor,示意自界说的线程池,为了留意高并发场景下,出现线程过多的问题。

3.2 数据异构

上头说到的用户信息查询接口需要调用用户查询接口、积分查询接口 和 成长值查询接口,然后汇总额据长入复返。

那么,咱们能弗成把数据冗余一下,把用户信息、积分和成长值的数据长入存储到一个方位,比如:redis,存的数据结构便是用户信息查询接口所需要的内容。然后通过用户id,平直从redis中查询数据出来,不就OK了?

若是在高并发的场景下,为了提高接口性能,资料接口调用大要率会被去掉,而改成保存冗尾数据的数据异构决策。

但需要扎眼的是,若是使用了数据异构决策,就可能会出现数据一致性问题。

用户信息、积分和成长值有更新的话,大部分情况下,会先更新到数据库,然后同步到redis。但这种跨库的操作,可能会导致双方数据不一致的情况产生。

4. 访佛调用

访佛调用在咱们的日常职责代码中不错说遍地可见,但若是莫得鸿沟好,会尽头影响接口的性能。

不信,咱们一道望望。

4.1 轮回查数据库

有时分,咱们需要从指定的用户不时中,查询出有哪些是在数据库中还是存在的。

罢了代码不错这么写:

public List<User> queryUser(List<User> searchList) {     if (CollectionUtils.isEmpty(searchList)) {         return Collections.emptyList();     }      List<User> result = Lists.newArrayList();     searchList.forEach(user -> result.add(userMapper.getUserById(user.getId())));     return result; } 

这里若是有50个用户,则需要轮回50次,去查询数据库。咱们都澄莹,每查询一次数据库,便是一次资料调用。

若是查询50次数据库,就有50次资料调用,这诟谇常耗时的操作。

那么,咱们如何优化呢?

具体代码如下:

public List<User> queryUser(List<User> searchList) {     if (CollectionUtils.isEmpty(searchList)) {         return Collections.emptyList();     }     List<Long> ids = searchList.stream().map(User::getId).collect(Collectors.toList());     return userMapper.getUserByIds(ids); } 

提供一个把柄用户id不时批量查询用户的接口,只资料调用一次,就能查询出所有这个词的数据。

这里有个需要扎眼的方位是:id不时的大小要做为止,最佳一次不要肯求太多的数据。要把柄骨子情况而定,残酷鸿沟每次肯求的记载条数在500以内。

4.2 死轮回

有些小伙伴看到这个标题,可能会感到有点不测,死轮回也算?

代码中不是应该幸免死轮回吗?为啥照旧会产死活轮回?

有时分死轮回是咱们我方写的,举例底下这段代码:

while(true) {     if(condition) {         break;     }     System.out.println("do samething"); } 

这里使用了while(true)的轮回调用,这种写法在CAS自旋锁中使用比拟多。

当知足condition等于true的时分,则自动退出该轮回。

若是condition条目尽头复杂,一朝出现判断不正确,或者少写了一些逻辑判断,就可能在某些场景下出现死轮回的问题。

出现死轮回,大要率是建立人员人为的bug导致的,不外这种情况很容易被测出来。

还有一种隐蔽的比拟深的死轮回,是由于代码写的不太严谨导致的。若是用平淡数据,可能测不出问题,但一朝出现额外数据,就会立即出现死轮回。

4.3 无尽递归

若是想要打印某个分类的所有这个词父分类,不错用类似这么的递归要领罢了:

public void printCategory(Category category) {   if(category == null