卡飞资源网

专业编程技术资源共享平台

SpringBoot一个提升N倍性能的操作

环境:springboot2.3.9.RELEASE + JPA + MySQL


一般我们在spring项目中都是在方法或者是类上添加事务支持,如下使用方式:

@Transactional
public Account deduction(Long id, BigDecimal money) {
    	Optional<Account> op = accountDAO.findById(id);
  	  if (!op.isPresent()) {
          throw new RuntimeException("不存在");
      }
      account.setMoney(account.getMoney().subtract(money)) ;
		  return accountDAO.saveAndFlush(account) ;
}

以上应该是我们在项目中使用事务的姿势了。这里是方法级别的事务,当方法执行的时候通过动态代理打开事务,执行代码,提交事务/回滚事务,执行的逻辑大体如下:

transaction.begin();
method.invoke(xxxx);
transaction.commit(); / transaction.rollback();

在上面举的示例比较简单,整个操作就是计算扣减金额,然后更新数据。这个业务也就是在保存数据的时候需要使用到事务,其它的一些计算是不需要在一个事务中的。想象下如果我们这里保存操作之上的代码,计算逻辑是个非常复杂的逻辑可能需要消耗好几秒甚至是十几秒而实际保存操作可能就几毫秒就完成了。我们又知道这方法级的事务在执行的时候是要先获取一个Connection对象(数据库连接对象的)然后打开事务(设置自动提交为false,connection.setAutoCommit(false));说到这你应该能想到,从获取一个Connection对象到释放需要几秒甚至是十几秒的时间,而占用的这些时间中大部分的时间都是与事务无关的操作也就是说是不需要事务的,而我们的数据库连接对象本身就是很宝贵及有限的,这就造成了我们系统的资源浪费,系统的吞吐量非常的低。接下来我们就来通过编程的方式控制事务提供系统的吞吐量。

  • 模拟常规的事务,展现低吞吐量操作

数据库连接配置:

spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/x?serverTimezone=GMT%2B8
    username: root
    password: xxxx
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimumIdle: 1
      maximumPoolSize: 1
      autoCommit: true
      idleTimeout: 30000
      poolName: MasterDatabookHikariCP
      maxLifetime: 1800000
      connectionTimeout: 30000
      connectionTestQuery: SELECT 1

这里把数据库连接池配置为1个。

Service中模拟耗时的操作

@Transactional
public Account deduction(Long id, BigDecimal money) {
    	System.out.println("Service 当前执行线程:" + Thread.currentThread().getName() + ", id = " + id + ", money = " + money) ;
    	Account account = accountDAO.findById(id).orElse(null) ;
    	if (account == null) {
    		return null ;
    	}
    	try {
			TimeUnit.SECONDS.sleep(10) ;
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
    	account.setMoney(account.getMoney().subtract(money)) ;
		return accountDAO.saveAndFlush(account) ;
}

Controller接口

@GetMapping("/deduction")
	public Object deductionAction(Long id, BigDecimal money) {
		System.out.println("Controller 当前线程:" + Thread.currentThread().getName()) ;
		return accountService.deduction(id, money) ;
	}

启动两个浏览器测试,观察控制台的输出

两个浏览器都还在转圈,没有响应。

控制台展示Controller方法都进入了,但是Service方法只进入了一个,因为我们的连接池只配置了一个,另外一个在等待可用的连接对象。而上面我也说了,其实Service中很长的一个计算耗时是不需要事务的,即便没有连接对象可用,我们也应该让这些不需要事务的操作也进行执行。接下来修改代码。

  • 编程事务,提高系统吞吐量
@Resource
    private TransactionTemplate transactionTemplate ;
    
    public Account deduction(Long id, BigDecimal money) {
    	System.out.println("Service 当前执行线程:" + Thread.currentThread().getName() + ", id = " + id + ", money = " + money) ;
    	Account account = accountDAO.findById(id).orElse(null) ;
    	if (account == null) {
    		return null ;
    	}
    	try {
			TimeUnit.SECONDS.sleep(10) ;
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
    	// 以上业务代码执行可能是个很耗时的操作。
    	return transactionTemplate.execute(new TransactionCallback<Account>() {
			@Override
			public Account doInTransaction(TransactionStatus status) {
				try {
					account.setMoney(account.getMoney().subtract(money)) ;
		    		return accountDAO.saveAndFlush(account) ;
				} catch (Exception e) {
					logger.error("发生错误:{}", e) ;
					status.setRollbackOnly() ;
				}
				return null ;
			}
		}) ;
    }

这里把方法上的事务注解删了,把需要事务的操作通过编程的方式包装,在Service中注入

TransactionTemplate对象,SpringBoot项目已经自动为我们配置好了,自动装配类:
TransactionAutoConfiguration.java

测试:

浏览器都还在转圈中,查看控制台:

2个Service方法都进去了,基本连接池只有一个连接对象,但是也不妨碍我非事务的代码执行,通过这样的改造,我们的系统吞吐量是不是提升了N呢?

完毕!!!

给个关注+转发呗谢谢










spring data jpa 高级应用

Spring MVC 异常处理方式

SpringBoot项目查看线上日志

SpringMVC参数统一验证方法

SpringBoot多数据源配置详解

Springboot中Redis事务的使用及Lua脚本

springboot mybatis jpa 实现读写分离

SpringBoot开发自己的Starter

SpringBoot RabbitMQ消息可靠发送与接收

SpringBoot中使用Cache及JSR107的使用

Springboot项目使用docker部署

SpringBoot整合Quartz实现任务调度

SpringBoot2 整合OAuth2实现统一认证

SpringMVC自定义注解实现接口调用

Springboot自定义消息转换器

springboot 数据安全传输加密与解密

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言