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

 更新时间:2023年01月03日 09:16:27   作者:JAVA旭阳  
作为后端开发人员,我们总是在编写各种API。这些API在服务初期可能表现不错,但随着用户数量的增长,一开始响应很快的API越来越慢,这时候你就需要考虑如何优化你的API性能了。在这篇文章中,我总结了一些行之有效的API性能优化技巧,希望能给有需要的朋友一些帮助

前言

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

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

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

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

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 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等分布式缓存中间件。

到此这篇关于分享5个Java接口性能提升的通用技巧的文章就介绍到这了,更多相关Java接口性能提升技巧内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 如何巧用HashMap一行代码统计单词出现次数详解

    如何巧用HashMap一行代码统计单词出现次数详解

    这篇文章主要给大家介绍了关于如何巧用HashMap一行代码统计单词出现次数的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2020-07-07
  • Java实现斗地主最简代码实例

    Java实现斗地主最简代码实例

    在本篇文章里小编给各位分享的是关于Java实现斗地主最简代码实例,有兴趣的朋友们可以参考下。
    2020-05-05
  • Spring ComponentScan的扫描过程解析

    Spring ComponentScan的扫描过程解析

    这篇文章主要介绍了spring ComponentScan的扫描过程解析,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • 使用jpa之动态插入与修改(重写save)

    使用jpa之动态插入与修改(重写save)

    这篇文章主要介绍了使用jpa之动态插入与修改(重写save),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Java经典用法总结(二)

    Java经典用法总结(二)

    这篇文章主要介绍了Java经典用法总结,在本文中,尽量收集一些java最常用的习惯用法,特别是很难猜到的用法,本文重点讲解了java应用和输入输出常用方法,感兴趣的小伙伴们可以参考一下
    2016-02-02
  • Java中properties文件中的中文乱码问题

    Java中properties文件中的中文乱码问题

    Properties为了方便用户的配置,用于读取Java的配置文件,不同的编程语言有自己所支持的配置文件,能让用户够脱离程序本身去修改相关的变量设置,这篇文章主要介绍了Java中properties文件中的中文乱码问题,需要的朋友可以参考下
    2023-08-08
  • Spring boot随机端口你都不会还怎么动态扩容

    Spring boot随机端口你都不会还怎么动态扩容

    这篇文章主要介绍了Spring boot随机端口你都不会还怎么动态扩容,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • springboot上传文件,url直接访问资源问题

    springboot上传文件,url直接访问资源问题

    这篇文章主要介绍了springboot上传文件,url直接访问资源问题。具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • Java算法中的归并排序算法代码实现

    Java算法中的归并排序算法代码实现

    这篇文章主要介绍了Java算法中的归并排序算法代码实现,归并排序使用的是分治思想(Divide and Conquer),分治,顾名思义,就是分而治之,是将一个大问题分解成小的子问题来解决,需要的朋友可以参考下
    2023-12-12
  • 为什么Java开发需要配置环境变量

    为什么Java开发需要配置环境变量

    这篇文章主要介绍了为什么Java开发需要配置环境变量,帮助大家更好的理解和学习Java,感兴趣的朋友可以了解下
    2020-08-08

最新评论