5个接口性能提升的通用技巧

news/2025/2/3 22:07:12/

前言

作为后端开发人员,我们总是在编写各种API,无论是为前端web提供数据支持的HTTP REST API ,还是提供内部使用的RPC API。这些API在服务初期可能表现不错,但随着用户数量的增长,一开始响应很快的API越来越慢,直到用户抱怨:“你的系统太糟糕了。” 我只是浏览网页。为什么这么慢?”。这时候你就需要考虑如何优化你的API性能了。

要想提高你的API的性能,我们首先要知道哪些问题会导致接口响应慢。API设计需要考虑很多方面。开发语言层面只占一小部分。哪个部分设计不好就会成为性能瓶颈。影响API性能的因素有很多,总结如下:

  • 数据库慢查询
  • 复杂的业务逻辑
  • 糟糕的代码
  • 资源不足

在这篇文章中,我总结了一些行之有效的API性能优化技巧,希望能给有需要的朋友一些帮助。

1. 并发调用

假设我们现在有一个电子商务系统需要提交订单。该功能需要调用库存系统进行库存查扣,还需要获取用户地址信息。最后调用风控系统判断本次交易无风险。这个接口的设计大部分可能会把接口设计成一个顺序执行的接口。毕竟我们需要获取到用户地址信息,完成库存扣减,才能进行下一步。伪代码如下:

public Boolean submitOrder(orderInfo orderInfo) {//check stockstockService.check();//invoke addressServiceaddressService.getByUser();//risk controlriskControlSerivce.check();return doSubmitOrder(orderInfo);
}

如果我们仔细分析这个函数,就会发现几个方法调用之间并没有很强的依赖关系。而且这三个系统的调用都比较耗时。假设这些系统的调用耗时分布如下

  • stockService.check()需要 150 毫秒。
  • addressService.getByUser()需要 200 毫秒。
  • riskControlSerivce.check()需要 300 毫秒。

如果顺序调用此API,则整个API的执行时间为650ms(150ms+200ms+300ms)。如果能转化为并行调用,API的执行时间为300ms,性能直接提升50%。使用并行调用,大致代码如下:

public Boolean submitOrder(orderInfo orderInfo) {//check stockCompletableFuture<Void> stockFuture = CompletableFuture.supplyAsync(() -> {return stockService.check(); }, executor);//invoke addressServiceCompletableFuture<Address> addressFuture = CompletableFuture.supplyAsync(() -> {return addressService.getByUser();}, executor);//risk controlCompletableFuture<Void> riskFuture = CompletableFuture.supplyAsync(() -> {return 	riskControlSerivce.check();}, executor);CompletableFuture.allOf(stockFuture, addressFuture, riskFuture);stockFuture.get();addressFuture.get();riskFuture.get();return doSubmitOrder(orderInfo);
}

2. 避免大事务

所谓大事务,就是历经时间很长的事务。如果使用Spring @Transaction管理事务,需要注意是否不小心启动了大事务。因为Spring的事务管理原理是将多个事务合并到一个执行中,如果一个API里面有多个数据库读写,而且这个API的并发访问量比较高,很可能大事务会导致太大大量数据锁在数据库中,造成大量阻塞,数据库连接池连接耗尽。

@Transactional(rollbackFor=Exception.class)
public Boolean submitOrder(orderInfo orderInfo) {//check stockstockService.check();//invoke addressServiceaddressService.getByUser();//risk controlriskControlRpcApi.check();orderService.insertOrder(orderInfo);orderDetailService.insertOrderDetail(orderInfo);return true;
}

相信在很多人写的业务中都出现过这种代码,远程调用操作,一个非DB操作,混合在持久层代码中,这种代码绝对是一个大事务。它不仅需要查询用户地址和扣除库存,还需要插入订单数据和订单明细。这一系列操作需要合并到同一个事务中。如果RPC响应慢,当前线程会一直占用数据库连接,导致并发场景下数据库连接耗尽。不仅如此,如果事务需要回滚,你的API响应也会因为回滚慢而变慢。

这个时候就需要考虑减小事务了,我们可以把非事务操作和事务操作分开,像这样:

@Autowired
private OrderDaoService orderDaoService;public Boolean submitOrder(OrderInfo orderInfo) {//invoke addressServiceaddressService.getByUser();//risk controlriskControlRpcApi.check();return orderDaoService.doSubmitOrder(orderInfo);
}@Service
public class OrderDaoService{@Transactional(rollbackFor=Exception.class)public Boolean doSubmitOrder(OrderInfo orderInfo) {//check stockstockService.check();orderService.insertOrder(orderInfo);orderDetailService.insertOrderDetail(orderInfo);return true;}
}

或者,您可以使用 spring 的编程事务TransactionTemplate

@Autowired
private TransactionTemplate transactionTemplate;public void submitOrder(OrderInfo orderInfo) {//invoke addressServiceaddressService.getByUser();//risk controlriskControlRpcApi.check();return transactionTemplate.execute(()->{return doSubmitOrder(orderInfo);})
}public Boolean doSubmitOrder(OrderInfo orderInfo) {//check stockstockService.check();orderService.insertOrder(orderInfo);orderDetailService.insertOrderDetail(orderInfo);return true;}

3. 添加合适的索引

我们的服务在运行初期,系统需要存储的数据量很小,可能是数据库没有加索引来快速存储和访问数据。但是随着业务的增长,单表数据量不断增加,数据库的查询性能变差。这时候我们应该给你的数据库表添加适当的索引。可以通过命令查看表的索引(这里以MySQL为例)。

show index from `your_table_name`;

ALTER TABLE通过命令添加索引。

ALTER TABLE `your_table_name` ADD INDEX index_name(username);

有时候,即使加了一些索引,数据查询还是很慢。这时候你可以使用explain命令查看执行计划来判断你的SQL语句是否命中了索引。例如:

explain select * from product_info where type=0;

你会得到一个分析结果:

一般来说,索引失效有几种情况:

  • 不满足最左前缀原则。例如,您创建一个组合索引idx(a,b,c)。但是你的SQL语句是这样写的select * from tb1 where b='xxx' and c='xxxx';
  • 索引列使用算术运算。select * from tb1 where a%10=0;
  • 索引列使用函数。select * from tb1 where date_format(a,'%m-%d-%Y')='2023-01-02';
  • like使用关键字的模糊查询。select * from tb1 where a like '%aaa';
  • 使用not innot exist关键字。
  • 等等

4. 返回更少的数据

如果我们查询大量符合条件的数据,我们不需要返回所有数据。我们可以通过分页的方式增量提供数据。这样,我们需要通过网络传输的数据更少,编码和解码数据的时间更短,API 响应更快。

但是,传统的limit offset方法用于 paging( select * from product limit 10000,20)。当页面数量很大时,查询会越来越慢。这是因为使用的原理limit offset是找出10000条数据,然后丢弃前面的9980条数据。我们可以使用延迟关联来优化此 SQL。

select * from product where id in (select id from product limit 10000,20);

5. 使用缓存

缓存是一种以空间换时间的解决方案。一些用户经常访问的数据直接缓存在内存中。因为内存的读取速度远快于磁盘IO,所以我们也可以通过适当的缓存来提高API的性能。简单的,我们可以使用Java的HashMapConcurrentHashMap,或者caffeine等本地缓存,或者MemcachedRedis等分布式缓存中间件。

总结

我在这里列出了五个通用的 API 性能优化技巧,这些技巧只有在系统有一定的并发压力时才有效。如果本文对你有帮助的话,请留下一个赞吧。


http://www.ppmy.cn/news/334275.html

相关文章

手机工作室网络如何组建?

现在很多的招商项目都是手机工作室的&#xff0c;包括做试玩、抖音还有做手游的&#xff0c;少则几十台手机&#xff0c;多则几百、上千台手机来做业务。 1. 组网问题 手机工作室很多人都会卡在组网上面&#xff0c;主要有三个问题&#xff1a; 1. 手机多了连不上WIFI&#…

这类开发者起薪30W,阿里腾讯华为抢着要......

(文末有惊喜) 前几天有个朋友在群里提问&#xff1a;如何看待大数据的未来&#xff1f;有必要转大数据方向吗&#xff1f; 关于这个问题&#xff0c;谈谈我的思考。伴随公有云厂商的兴起&#xff0c;大数据的应用进入了2.0时代。 传统大数据那种需要大量购买机器以及Hadoop发行…

基于Docker 部署gitlab-ce

1. 获取Gitlab镜像 1.1 查找Gitlab镜像 # 查找Gitlab镜像 $ sudo docker search gitlab-ce NAME DESCRIPTION STARS OFFICIAL AUTOMATED gitlab/gitlab-ce GitLab C…

解读CPU缓存,它们如何工作的?

究竟什么是 CPU 缓存&#xff1f;为什么有 L1、L2 和 L3 缓存&#xff1f;以下是您需要了解的内容以及它们的工作原理。 近年来&#xff0c;计算机中央处理器已经取得了相当大的进步&#xff0c;晶体管每年都在变小&#xff0c;性能也变得更加强大。每当提到处理器的性能时&am…

JVM调优命令、调优思路

文章目录 JVM调优工具jmap命令jstack命令查线程死锁查占有cpu资源 远程连接jvisualvmjinfo命令jstat垃圾回收统计堆内存统计新生代垃圾回收统计新生代内存统计老年代垃圾回收统计老年代内存统计元数据空间统计各内存区使用比例 JVM运行问题排查思路 JVM调优工具 jmap命令 主要…

卡牌游戏源代码(原创)(控制台)

游戏预览&#xff1a; 完成度90%&#xff0c;约3000行&#xff0c;过年这几天全用在这上面了 由于尚未学到QT等&#xff0c;因此只能在黑窗口下面制作了 未完成的部分&#xff1a; ①战斗代码未优化&#xff08;800行&#xff0c;精简后应该能降到200行左右&#xff09; ②关…

【burpsuite安全练兵场-客户端11】跨站点脚本XSS-20个实验(上)

前言&#xff1a; 介绍&#xff1a; 博主&#xff1a;网络安全领域狂热爱好者&#xff08;承诺在CSDN永久无偿分享文章&#xff09;。 殊荣&#xff1a;CSDN网络安全领域优质创作者&#xff0c;2022年双十一业务安全保卫战-某厂第一名&#xff0c;某厂特邀数字业务安全研究员&…

第十九期 基于HG255d_U-Boot的uIP移植《路由器就是开发板》

前面三期我们大略了解了U-Boot的三个常用功能&#xff0c;串口调试输入输出&#xff0c;flash操作&#xff0c;和网络连接&#xff0c;这一期我们来进行U-Boot的改造&#xff0c;我们改造的主题是优化U-Boot的网络功能&#xff0c;我们最终的目的是优化U-Boot的交互界面&#x…