前言

作为后端开发人员,咱们总是在编写各种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;

你会得到一个剖析结果:

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

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

  • 不满足最左前缀准则。例如,您创建一个组合索引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的功能。简略的,咱们能够运用JavaHashMapConcurrentHashMap,或者caffeine等本地缓存,或者MemcachedRedis等分布式缓存中间件。

总结

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

欢迎重视个人大众号『JAVA旭阳』交流交流