必备技能关于接口的性能优化

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情

前言

在日常工作的开发中,我们通常开发完接口,使接口业务能正常执行的情况下,我们还是要关注接口的性能,主要体现在接口的执行时间,如果遇到执行比较慢的接口,我们就需要对它进行优化了,从而来提升对整个服务的性能,作为一个工作几年的后端开发,接口的性能优化是我们必备的技能。

如何看接口的执行时间

通常我们需要去优化接口,首先要去定位到我们的慢接口是哪个,那么定位到慢接口有哪些方式呢?

1.链路追踪 Tracing Analysis 通过阿里的链路追踪插件,可以查看接口调用的性能指标,类似的插件平台还有很多种,这里重要列举一种。

p345199.png

以及调用链每个环节涉及到的接口请求相应时间,都可以通过链路追踪获取到。

p344943.png

2.AOP切面 使用AOP自定义注解,使用切面的方式对接口实行前置、后置、环绕的一个处理,记录接口调用的情况,并使用log表把数据记录在表中,通过数据库的字段来看接口的执行效率。

慢接口如何进行优化

1.字段加索引

索引优化是首先想到的,我们可以对访问DB的表查询频率比较高的一些字段加索引。 索引的类型有:普通索引、主键索引、唯一索引、复合索引、全文索引、前缀索引;

开发过程中比较常见的就是加复合索引,也就是通过where后面的条件进行加索引,通过最左匹配的原则来进行检索。

当然我们还要考虑哪些字段适合加索引,一般来说我们需要在主键字段、非空字段、字段长度较短、更新不频繁的字段、分组字段或排序字段都应该加索引的。

CREATE INDEX idx_name ON table (a,b,c);

2.sql优化

Sql优化我就不举例子,转载了一篇别人总结不错的文章供大家参考 Sql优化总结!详细!(2021最新面试必问)_布诺i的博客-CSDN博客_sql优化 转自-csdn,作者: 布诺i

3.数据分页

对于请求大批次数据,我们不应该对数据进行全部的批量处理,可以进行数据分页,分批次对数据进行批量处理,最后进行整合。 可以通过limit来限制我们一次处理的条数。

4.批量请求接口

这样可以一次性通过调用接口来拿到所需的数据,减少了对接口的访问,避免了创建连接产生的开销。

实际代码我们可以这样写:

final List<String> applyCds = resultModelList.stream().map(TReconsiderVO::getApplyCd).collect(Collectors.toList());
final List<BriefOrderModel> orderList = orderCommonService.getOrderList(applyCds);

循环里面去操作数据库,也可以利用Mybatis的批量请求数据库。

5.并行请求接口

在某些特定的业务场景下,我们的接口方法中去调用其他服务的方法,比如服务A、B、C,我们的业务逻辑先调A服务,我们不用等待接口的返回在去进行业务逻辑的判断的情况下,这样串行请求会使我们的接口响应累加变长。

3DFC697C-D8BE-4086-9BB6-DC3A32B70343.png

所以要考虑进行并行请求接口,Java8引入了 CompletableFuture 异步线程请求接口。 并行请求接口就可以在200ms的时间去连续请求三个接口

823575A7-05CD-4695-83B4-422190B09BAA.png

代码示例:

public void getAsync() throws ExecutionException, InterruptedException {

    ExecutorService executor = Executors.newFixedThreadPool(5);
    CompletableFuture aFuture = CompletableFuture.supplyAsync(() -> {

        return "执行A服务返回结果";
    }, executor);
    CompletableFuture bFuture = CompletableFuture.supplyAsync(() -> {

        return "执行B服务返回结果";
    }, executor);
    CompletableFuture cFuture = CompletableFuture.supplyAsync(() -> {

        return "执行C服务返回结果";
    }, executor);

    CompletableFuture.allOf(aFuture, bFuture, cFuture).join();

    aFuture.get();
    bFuture.get();
    cFuture.get();

}

6.mq异步请求

例如像这种生成订单、发短信业务,接口的请求量就比较大,可以采用mq消息中间件,可以在请求量大的情况下进行削峰,把所有请求都发送到mq服务上面进行接收,因为发送mq是相当快的,这样接口可以专注于业务,剩下的交给mq来解决,当然维护消息中间件也是需要花功夫的。

47483D7E-04E4-41E8-80A6-318DE6F447ED.png

7.加入缓存

缓存现在是每个分布式系统必不可少的中间件,在高并发的场景下,通过访问我们的业务接口,如果是频繁请求数据量比较大的接口是比较耗时间的,合理的利用缓存,可以让我们避免一些重复获取数据的操作,从而大大的减少我们接口的请求时间。

同时使用redis缓存也要注意我们缓存和数据库一致性如何保持,还有就是要考虑缓存穿透、缓存击穿、缓存雪崩的情况,包括对key的设计也要合理,避免Big key 影响性能。

二级缓存

也可以通过定义二级缓存,Mybatis、Guava、Spring cache,这些都是本地缓存,在接口访问我们本地数据库拿数据的时候,可以开启二级缓存。 当然也是要注意对于频繁更新的数据不要放入二级缓存,很容易出现缓存数据库不一致的情况。

8.分布式环境下锁的颗粒度

加锁是为了避免并发的情况下来保证数据的一致性,但是也是需要控制锁的颗粒度,我们需要合理的去选择加锁的对象,尽量去减小锁的颗粒度,如果锁的粒度比较大对于类、方法、以及调用方法的方法进行加锁,都会去影响接口的吞吐量。 尽量去选择全局唯一业务code、订单号这种加锁,也要设置获取锁的时间和过多长时间锁会自动释放,业务处理完记得一定要去释放锁,避免死锁情况。

final String applycdKey = String.format(Constants.REDIS_LOCK_KEY_APPLYCD, applyCd);
final RLock applycdLock = RedissonClientUtil.getFairLock(applycdKey);
try {
    if (applycdLock.tryLock(10, 10, TimeUnit.SECONDS)) {
//业务逻辑
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}finally {
    //释放applycd锁
    applycdLock.unlock();
}

总结

总而言之,我们作为一个合格的程序员,肯定要对自己写的接口进行优化,从而保证整个系统的运行效率。当然我们对接口速度优化的同时,也会带来其他问题,也不能一味的的追求性能,也要做到性能和稳定性兼顾,这才是作为一个出色的程序员应该做的。

猜你喜欢

转载自juejin.im/post/7127124633995706404