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

接口,优化,常见,方案,实战,总结 · 浏览次数 : 85

小编点评

**接口性能优化方案** **一、批处理批量思想** * 批量操作数据库,避免多次IO。 * 使用线程池、消息队列等技术实现异步处理。 **二、异步处理** * 针对耗时比较长且不是结果必须的逻辑,放到异步执行。 * 例如:理财的申购接口,入账和写入申购文件是同步执行的,因为是T+1交易,后面这两个逻辑其实不是结果必须的,我们并不需要关注它的实时结果,所以我们考虑把入账和写入申购文件异步处理。 **三、事务** * 避免处理太多数据。 * 通过事务保证数据的一致性。 **四、深分页** * 优化SQL以提高查询性能。 * 例如:使用标签记录法来优化查询。 **五、SQL优化** * 减少锁粒度。 * 使用非共享资源的锁机制。 **六、锁粒度** * 调整锁的粒度,以避免过粗锁。 * 例如:使用同步锁或分布式锁的粒度为读写。 **七、错误的加锁方式** * 非共享资源: * `notShare()`: 尝试获取锁时,如果获取失败,就直接返回。 * `share()`: 尝试获取锁后,如果获取成功,则释放锁。 * 共享资源: * `share()`: 尝试获取锁后,如果获取成功,则释放锁。 * `notShare()`: 尝试获取锁时,如果获取失败,则阻塞等待。

正文

一、背景

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

二、接口优化方案总结

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();

            }
        } 

三、最后

接口性能问题形成的原因思考

我相信很多接口的效率问题不是一朝一夕形成的,在需求迭代的过程中,为了需求快速上线,采取直接累加代码的方式去实现功能,这样会造成以上这些接口性能问题。

变换思路,更高一级思考问题,站在接口设计者的角度去开发需求,会避免很多这样的问题,也是降本增效的一种行之有效的方式。

以上,共勉!

与接口优化的常见方案实战总结相似的内容:

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

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

前端说你的API接口太慢了,怎么办?

当有千万条海量数据时,前端调取接口发现接口响应的太慢,前端这时让你优化一下接口,你说有几千万条数据,觉得自己尽力了,前端觉得你好菜,别急,读完这篇文章,让前端喊你一声:大佬,厉害!!! 常用的方法总结 通过合理的分页加载、索引优化、数据缓存、异步处理、压缩数据等手段,可以有效地优化接口性能,提升系统

深入了解Elasticsearch搜索引擎篇:倒排索引、架构设计与优化策略

首先,我们介绍了Elasticsearch(ES)的倒排索引,这是一种用于快速检索的数据结构。其次,我们了解了ES集群的架构,包括主节点、数据节点和协调节点的功能和作用。然后,我们探讨了中文分词器的选择,其中包括IK、HanLP和Jieba等常用的分词工具。接着,我们解释了写入数据和查询数据的工作原理,包括请求的分配和预处理,数据的存储和查询结果的处理过程。最后,我们讨论了ES部署的优化方法,包括调整JVM内存、分片布局和数量、节点身份设计以及配置Ingest节点等方面的策略。

从 5s 到 0.5s!CompletableFuture 异步任务优化技巧,确实优雅!

一个接口可能需要调用 N 个其他服务的接口,这在项目开发中还是挺常见的。举个例子:用户请求获取订单信息,可能需要调用用户信息、商品详情、物流信息、商品推荐等接口,最后再汇总数据统一返回。 如果是串行(按顺序依次执行每个任务)执行的话,接口的响应速度会非常慢。考虑到这些接口之间有大部分都是 无前后顺序

Dart常用核心知识

Dart简述 Dart 是一个为全平台构建快速应用的客户端优化的编程语言,免费且开源。 Dart是面向对象的、类定义的、单继承的语言。它的语法涵盖了多种语言的语法特性,如C,JavaScirpt, Java, Swift等语言,可以转译为JavaScript,支持接口(interfaces)、混入(

如何优雅地退出程序

在Linux系统中执行操作时,进程可以通过发送和接收信号与其他进程进行通信。信号是用于通知进程发生特定事件或请求进程采取特定动作的软件中断。 以下是Linux系统中常见的一些信号及其含义: 1. **SIGINT (2)**:中断信号,通常由终端用户按下`Ctrl + C`发送给前台进程。该信号用于

「硬核」实操如何拥有一个自己的数字人模型

本文大致分为三个章节: 第一部分会介绍常用技术以及简介,以及自己用到构建一个自己虚拟数字人用到的3D引擎和三维软件 第二部分会详细描述生成细节,通过照片建模,创建模型,添加细节,添加表情,优化效果,接入场景,最终效果 第三部分总结本篇内容

SQLite vs MySQL vs PostgreSQL对比总结

开发业务系统时,是绕不开RDBMS(关系型数据库)的。虽然现在诞生了各种NoSQL的数据库,RDBMS在业务系统中的严谨和优势依然无法取代。 近几年大大小小的项目中,常用的三种RDBMS(SQLite,MySQL,Postgres)都有多次接触过,一些使用心得记录如下,供大家参考。 1. SQLit

《优化接口设计的思路》系列:第十一篇—表格的导入导出接口优化

一、前言 大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。 作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,

《优化接口设计的思路》系列:第十篇—网站的静态资源怎么获取?

一、前言 大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。 作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,