前言
作为后端开发人员,咱们总是在编写各种API,无论是为前端web提供数据支撑的HTTP REST API
,仍是提供内部运用的RPC API
。这些API在服务初期或许表现不错,但随着用户数量的增长,一开始呼应很快的API越来越慢,直到用户抱怨:“你的体系太糟糕了。” 我仅仅浏览网页。为什么这么慢?”。这时候你就需求考虑如何优化你的API功能了。
要想进步你的API的功能,咱们首先要知道哪些问题会导致接口呼应慢。API规划需求考虑许多方面。开发语言层面只占一小部分。哪个部分规划欠好就会成为功能瓶颈。影响API功能的因素有许多,总结如下:
- 数据库慢查询
- 杂乱的业务逻辑
- 糟糕的代码
- 资源缺乏
- ……..
在这篇文章中,我总结了一些行之有效的API功能优化技巧,希望能给有需求的朋友一些帮助。
欢迎重视个人大众号『JAVA旭阳』交流交流
1. 并发调用
假定咱们现在有一个电子商务体系需求提交订单。该功能需求调用库存体系进行库存查扣,还需求获取用户地址信息。最终调用风控体系判别本次买卖无风险。这个接口的规划大部分或许会把接口规划成一个顺序履行的接口。究竟咱们需求获取到用户地址信息,完成库存扣减,才能进行下一步。伪代码如下:
public Boolean submitOrder(orderInfo orderInfo) {
//check stock
stockService.check();
//invoke addressService
addressService.getByUser();
//risk control
riskControlSerivce.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 stock
CompletableFuture<Void> stockFuture = CompletableFuture.supplyAsync(() -> {
return stockService.check();
}, executor);
//invoke addressService
CompletableFuture<Address> addressFuture = CompletableFuture.supplyAsync(() -> {
return addressService.getByUser();
}, executor);
//risk control
CompletableFuture<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 stock
stockService.check();
//invoke addressService
addressService.getByUser();
//risk control
riskControlRpcApi.check();
orderService.insertOrder(orderInfo);
orderDetailService.insertOrderDetail(orderInfo);
return true;
}
信任在许多人写的业务中都出现过这种代码,远程调用操作,一个非DB操作,混合在持久层代码中,这种代码绝对是一个大业务。它不仅需求查询用户地址和扣除库存,还需求刺进订单数据和订单明细。这一系列操作需求合并到同一个业务中。假如RPC呼应慢,当时线程会一直占用数据库衔接,导致并发场景下数据库衔接耗尽。不仅如此,假如业务需求回滚,你的API呼应也会由于回滚慢而变慢。
这个时候就需求考虑减小业务了,咱们能够把非业务操作和业务操作分开,像这样:
@Autowired
private OrderDaoService orderDaoService;
public Boolean submitOrder(OrderInfo orderInfo) {
//invoke addressService
addressService.getByUser();
//risk control
riskControlRpcApi.check();
return orderDaoService.doSubmitOrder(orderInfo);
}
@Service
public class OrderDaoService{
@Transactional(rollbackFor=Exception.class)
public Boolean doSubmitOrder(OrderInfo orderInfo) {
//check stock
stockService.check();
orderService.insertOrder(orderInfo);
orderDetailService.insertOrderDetail(orderInfo);
return true;
}
}
或者,您能够运用 spring 的编程业务TransactionTemplate
。
@Autowired
private TransactionTemplate transactionTemplate;
public void submitOrder(OrderInfo orderInfo) {
//invoke addressService
addressService.getByUser();
//risk control
riskControlRpcApi.check();
return transactionTemplate.execute(()->{
return doSubmitOrder(orderInfo);
})
}
public Boolean doSubmitOrder(OrderInfo orderInfo) {
//check stock
stockService.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 in
或not 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的HashMap
、ConcurrentHashMap
,或者caffeine
等本地缓存,或者Memcached
、Redis
等分布式缓存中间件。
总结
我在这儿列出了五个通用的 API 功能优化技巧,这些技巧只要在体系有一定的并发压力时才有效。假如本文对你有帮助的话,请留下一个赞吧。
欢迎重视个人大众号『JAVA旭阳』交流交流