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

一、背景

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







二、接口优化方案总结

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

            }
        } 

三、最后

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/12040.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

设计模式-结构型模式之装饰模式

3. 装饰模式3.1. 模式动机一般有两种方式可以实现给一个类或对象增加行为:继承机制使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制…

java基础——迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 第一章 Iterator迭代器 1.1 Iterator接口 在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator。 想要遍历Collection集合&…

链表基础知识

1.链表必知必会 什么是链表? 链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思&#…

【好刊推荐】知名出版社影响因子7+被踢出SCI,投稿前如何选期刊?

今年3月Hindawi旗下的19本期刊被SCIE剔除,其中有一本影响因子7,以下从期刊各个指标方面分析一下具体原因: 期刊剔除:影响因子7 期刊简介 期刊名称: OXIDATIVE MEDICINE AND CELLULAR LONGEVITY ISSN / eISSN&#…

数据结构——堆和优先队列

文章目录前言堆堆的引入堆的定义堆的储存结构优先队列优先队列简介优先队列的基础操作入队出队优先队列的实现堆的应用堆排序TOP-K问题什么是TOP-K问题TOP-K问题的排序解法TOP-K问题的堆解法总结前言 堆是一个比较基础,且实现起来难度也不算太大的一个数据结构。而…

可选择的Elasticsearch好用的可视化客户端工具

前言 常言道:工欲善其事,必先利其器。对于我们开发和测试同学来说,在日常的工作中有一款趁手的工具那真实如虎添翼啊,工作效率可是蹭蹭蹭的往上长,节省下来的时间摸摸鱼该有多好啊。最近我们系统开始使用elasticsearc…

Spring注解开发

定义bean 我们先直接通过一张图来理解注解在Spring开发中如和定义bean: 那么我们既然加了注解,相当于定义了bean可是Spring的配置文件怎么知道他的存在,于是我们引入component-scan进行包扫描即为了让Spring框架能够扫描到写在类上的注解&…

Lego- 美团接口自动化测试实战(详细解析)

目录:导读 一、概述 1.1 接口自动化概述 1.2 提高 ROI 1.3 Lego 的组成 二、脚本设计 2.1 Lego 的做法 2.2 测试脚本 2.3 配置文件 三、用例设计 3.1 一些思考 3.2 Lego 接口自动化测试用例 3.3 参数化 3.4 前后置动作 3.5 执行各部分 四、网站功能 …

八百字讲清楚——BCEWithLogitsLoss二分类损失函数

BCEWithLogitsLoss是一种用于二分类问题的损失函数,它将Sigmoid函数和二元交叉熵损失结合在一起。 假设我们有一个大小为NNN的二分类问题,其中每个样本xix_ixi​有一个二元标签yi∈0,1y_i\in {0,1}yi​∈0,1,并且我们希望预测每个样本的概率…

Seal AppManager发布:基于平台工程理念的全新应用部署管理体验

4月12日,数澈软件Seal(以下简称“Seal”)宣布推出新一代应用统一部署管理平台 Seal AppManager,采用平台工程的理念,降低基础设施操作的复杂度为研发和运维团队提供易用、一致的应用管理和部署体验,进而提升…

28岁,他是如何成为上市公司测试总监的

现在的大环境下,各行各业都开始内卷起来,测试也不例外,企业要求也越来越高,“会代码”逐渐成为测试工程师的一个标签。你要想拿到一个不错的薪资,必不可少的一个技能—自动化测试,自动化测试难吗&#xff1…

【2023最新】超详细图文保姆级教程:App开发新手入门(5)

上文回顾,我们已经完成了一个应用的真机调试,本章我们来了解一下如何引入YonBuilder移动开发的(原生)移动插件, 并利用移动插件完成一个简单的视频播放器。 8. 「移动插件」的使用 8.1 什么是 「移动插件」? 用通俗…

HDLBits-Modules 题解【Verilog模块例化】(中文翻译+英文原文,可顺带学习英文)

Moudule 概念介绍 到目前为止,你已经熟悉了一个模块,它是一个通过输入和输出端口与其外部交互的电路。更大、更复杂的电路是通过将较小的模块和其他连接在一起的部分(例如赋值语句和always块)组合而成的更大模块来构建的。因为模…

对决:Kubernetes vs Docker Swarm - 谁才是最优秀的容器编排方案?

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 文章目录一、介绍1. 什么是Kubernetes2. 什么是Docker Swarm3. 为什么需要容器编排?二、 架构比较1. Kubern…

C++【栈队列(3种)反向迭代器】

文章目录一、容器适配器二、栈(一)栈定义(二)栈使用接口(三)栈模拟实现(1) 栈模拟实现解析(2) 栈模拟实现代码(3) 栈模拟结果三、队列(一)普通队列(1)普通队列…

30天学会《Streamlit》(3)

30学会《Streamlit》是一项编码挑战,旨在帮助您开始构建Streamlit应用程序。特别是,您将能够: 为构建Streamlit应用程序设置编码环境 构建您的第一个Streamlit应用程序 了解用于Streamlit应用程序的所有很棒的输入/输出小部件 第3天 - st.…

实验三、图像复原

1. 实验目的 (1) 理解退化模型。 (2) 掌握常用的图像复原方法。 2. 实验内容 (1) 模拟噪声的行为和影响的能力是图像复原的核心。 示例 1 :使用 imnoise 添加噪声。 J imnoise(I,gaussian) 将方差为 0.01 的零均值高斯白噪声添加到灰度图像 I。 J imnoise(I,g…

最近ChatGPT封号太严重了,这里是解封攻略步骤(建议收藏)

这个周末,先是意大利暂时封杀ChatGPT,限制OpenAI处理本国用户信息。 接着,据韩国媒体报道,三星导入ChatGPT不到20天,便曝出机密资料外泄。 还没结束,又有大量网友发现ChatGPT目前停止注册,开始…

​力扣解法汇总1026. 节点与其祖先之间的最大差值

目录链接: 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目: https://github.com/September26/java-algorithms 原题链接:力扣 描述: 给定二叉树的根节点 root,找出存在于 不同 节点 A 和 B 之间的最大值…

Samba共享

关闭selinux跟防火墙 setenforce 0 systemctl stop firewalld 安装samba以及客户端 yum install samba samba-client -y 创建共享目录 mkdir -p /data/share1 mkdir -p /data/public 添加samba用户并配置权限 useradd zsuser smbpasswd -a zsuser 修改配置文件并重启服…