关于接口:接口优化的常见方案实战总结

2次阅读

共计 3840 个字符,预计需要花费 10 分钟才能阅读完成。

一、背景

针对老我的项目,去年做了许多降本增效的事件,其中发现最多的就是接口耗时过长的问题,就集中搞了一次接口性能优化。本文将给小伙伴们分享一下接口优化的通用计划。

二、接口优化计划总结

1. 批处理

批量思维:批量操作数据库,这个很好了解,咱们在循环插入场景的接口中,能够在批处理执行实现后一次性插入或更新数据库,防止屡次 IO。

//for 循环单笔入库
list.stream().forEatch(msg->{insert();
});
// 批量入库
batchInsert();

2. 异步解决

异步思维:针对耗时比拟长且不是后果必须的逻辑,咱们能够思考放到异步执行,这样能升高接口耗时。

例如一个理财的申购接口,入账 写入申购文件 是同步执行的,因为是 T + 1 交易,前面这两个逻辑其实不是后果必须的,咱们并不需要关注它的实时后果,所以咱们思考把 入账 写入申购文件 改为异步解决。如图所示:

至于异步的实现形式,能够用线程池,也能够用音讯队列,还能够用一些调度工作框架。

3. 空间换工夫

一个很好了解的 空间换工夫 的例子是正当应用缓存,针对一些频繁应用且不频繁变更的数据,能够提前缓存起来,须要时间接查缓存,防止频繁地查询数据库或者反复计算。

须要留神的事,这里用了正当二字,因为空间换工夫也是一把双刃剑,须要综合思考你的应用场景,毕竟缓存带来的数据一致性问题也挺令人头疼。

这里的缓存能够是 R2M,也能够是本地缓存、memcached,或者 Map。

举一个股票工具的查问例子:

因为策略轮动的调仓信息,每周只更新一次,所以原来的调接口就去查库的逻辑并不合理,而且拿到调仓信息后,须要通过简单计算,最终得出回测收益和跑赢沪深指数这些咱们想要的后果。如果咱们把查库操作和计算结果放入缓存,能够节俭很多的执行工夫。如图:

4. 预处理

也就是预取思维,就是提前要把查问的数据,提前计算好,放入缓存或者表中的某个字段,用的时候会大幅提高接口性能。跟下面那个例子很像,然而关注点不同。

举个简略的例子:理财产品,会有依据净值计算年化收益率的数据展现需要,利用净值去套用年化收益率计算公式计算的逻辑咱们能够采纳预处理,这样每一次接口调用间接取对应字段就能够了。

5. 池化思维

咱们都用过数据库连接池,线程池等,这就是池思维的体现,它们解决的问题就是防止反复创建对象或创立连贯,能够反复利用,防止不必要的损耗,毕竟创立销毁也会占用工夫。

池化思维蕴含但并不局限于以上两种,总的来说池化思维的实质是 预调配与循环应用,明确这个原理后,咱们即便是在做一些业务场景的需要时,也能够利用起来。

比方:对象池

6. 串行改并行

串行就是,以后执行逻辑必须等上一个执行逻辑完结之后才执行,并行就是两个执行逻辑互不烦扰,所以并行相对来说就比拟节省时间,当然是建设在没有后果参数依赖的前提下。

比方,理财的持仓信息展现接口,咱们既须要查问用户的账户信息,也须要查问商品信息和 banner 位信息等等来渲染持仓页,如果是串行,基本上接口耗时就是累加的。如果是并行,接口耗时将大大降低。

如图:

7. 索引

加索引能大大提高数据查问效率,这个在接口设计之出也会思考到,这里不再多赘述,随着需要的迭代,咱们重点整顿一下索引不失效的一些场景,心愿对小伙伴们有所帮忙。

具体不失效场景不再一一举例,前面有工夫的话,独自整顿一下。

8. 防止大事务

所谓大事务问题,就是 运行工夫较长的事务,因为事务统一不提交,会导致数据库连贯被占用,影响到别的申请拜访数据库,影响别的接口性能。

举个例子:

    @Transactional(value = "taskTransactionManager", propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = {RuntimeException.class, Exception.class})
    public BasicResult purchaseRequest(PurchaseRecord record) {BasicResult result = new BasicResult();
        // 插入账户工作
        taskMapper.insert(ManagerParamUtil.buildTask(record, TaskEnum.Task_type.pension_account.type(), TaskEnum.Account_bizType.purchase_request.type()));
        // 插入同步工作
        taskMapper.insert(ManagerParamUtil.buildTask(record, TaskEnum.Task_type.pension_sync.type(), TaskEnum.Sync_bizType.purchase.type()));
        // 插入影像件上传工作
        taskMapper.insert(ManagerParamUtil.buildTask(record, TaskEnum.Task_type.pension_sync.type(), TaskEnum.Sync_bizType.cert.type()));
        result.setInfo(ResultInfoEnum.SUCCESS);
        return result;
    }

下面这块代码次要是申购申请实现后,执行一系列的后续操作,如果当初新增申购实现后,发送 push 告诉用户的需要。很有可能咱们会在前面间接追加,如下图所示:事务中嵌套 RPC 调用,即非 DB 操作,这些非 DB 操作如果耗时较大的话,可能会呈现大事务问题。大数据引发的问题次要有:死锁、接口超时、主从提早等。

    @Transactional(value = "taskTransactionManager", propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = {RuntimeException.class, Exception.class})
    public BasicResult purchaseRequest(PurchaseRecord record) {BasicResult result = new BasicResult();
        ...
        pushRpc.doPush(record);        
        result.setInfo(ResultInfoEnum.SUCCESS);
        return result;
    }

所以为防止大事务问题,咱们能够通过以下计划躲避:

1,RPC 调用不放到事务外面

2,查问操作尽量放到事务之外

3,事务中防止解决太多数据

9. 优化程序结构

程序结构问题个别呈现在屡次需要迭代后,代码叠加造成。会造成一些反复查问、屡次创建对象等耗时问题。在多人保护一个我的项目时比拟多见。解决起来也比较简单,咱们须要针对接口整体做重构,评估每个代码块的作用和用处,调整执行程序。

10. 深分页问题

深分页问题比拟常见,分页咱们个别最先想到的就是 limit,为什么会慢,咱们能够看下这个 SQL:

    select * from purchase_record where productCode = 'PA9044' and status=4 order by orderTime desc limit 100000,200 

limit 100000,200 意味着会扫描 100200 行,而后返回 200 行,抛弃掉前 100000 行。所以执行速度很慢。个别能够采纳标签记录法来优化,比方:

    select * from purchase_record where productCode = 'PA9044' and status=4 and id > 100000 limit 200

这样优化的益处是命中了主键索引,无论多少页,性能都还不错,然而局限性是须要一个间断自增的字段

11.SQL 优化

sql 优化能大幅提高接口的查问性能,因为本文重点讲述接口优化的计划,具体 sql 优化不再一一列举,小伙伴们能够联合索引、分页、等关注点思考优化计划。

12. 锁粒度防止过粗

锁个别是为了在高并发场景下爱护共享资源采纳的一种伎俩,然而如果锁的粒度太粗,会很影响接口性能。

对于锁粒度:就是你要锁的范畴有多大,不论是 synchronized 还是 redis 分布式锁,只须要在临界资源处加锁即可,不波及共享资源的,不必要加锁,就好比你要上卫生间,只须要把卫生间的门锁上就能够,不须要把客厅的门也锁上。

谬误的加锁形式:

        // 非共享资源
        private void notShare(){}
        // 共享资源
        private void share(){}
        private int wrong(){synchronized (this) {share();
                notShare();}
        }

正确的加锁形式:

       // 非共享资源
        private void notShare(){}
        // 共享资源
        private void share(){}
        private int right(){notShare();
            synchronized (this) {share();

            }
        } 

三、最初

接口性能问题造成的起因思考

我置信很多接口的效率问题不是久而久之造成的,在需要迭代的过程中,为了需要疾速上线,采取间接累加代码的形式去实现性能,这样会造成以上这些接口性能问题。

变换思路,更高一级思考问题,站在接口设计者的角度去开发需要,会防止很多这样的问题,也是降本增效的一种卓有成效的形式。

以上,共勉!

正文完
 0