SpringBoot项目中异步调用接口方式知多少?

环境:springboot2.3.9.RELEASE


经常会遇到在项目中调用第三方接口的情景,你是如何调用的呢?同步?异步?

场景:

假设下单业务流程如下步骤:

1、查询用户信息。

2、查询库存信息。

3、查询活动信息(折扣)。


  1. 同步顺序调用
public boolean createOrder() {
		long start = System.currentTimeMillis() ;
		String userResult = restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ;
		String storageResult = restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ;
		String discountResult = restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1}) ;
		// 这里合并请求结果处理
		System.out.println(Arrays.toString(new String[] {userResult, storageResult, discountResult})) ;
		System.out.println("传统方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ;
		return true ;
	}
  @GetMapping("/create")
	public Object create() {
		return os.createOrder() ;
	}

调用结果:

SpringBoot项目中异步调用接口方式知多少?

 

接口一个一个调用,非常耗时。

  1. 多线程(Callable+Future)
public boolean createOrder2() {
		long start = System.currentTimeMillis() ;
		Callable<String> userCallable = () -> {
			return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ;
		} ;
		Callable<String> storageCallable = () -> {
			return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ;
		} ;
		Callable<String> discountCallable = () -> {
			return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1}) ;
		} ;
		FutureTask<String> userTask = new FutureTask<>(userCallable) ;
		FutureTask<String> storageTask = new FutureTask<>(storageCallable) ;
		FutureTask<String> discountTask = new FutureTask<>(discountCallable) ;
		new Thread(userTask).start() ;
		new Thread(storageTask).start() ;
		new Thread(discountTask).start() ;
		try {
			String userResult = userTask.get() ;
			String storageResult = storageTask.get() ;
			String discountResult = discountTask.get() ;
			// 这里合并请求结果处理
			System.out.println(Arrays.toString(new String[] {userResult, storageResult, discountResult})) ;
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
		System.out.println("多线程方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ;
		return true ;
	}

调用结果:

SpringBoot项目中异步调用接口方式知多少?

 

这次耗时少了,性能明显提升了。但在项目中我们一般是禁止直接创建线程的,如果这是个高并发的接口,那么我们的程序很可能出现OOM的错误。

 

  1. 线程池(Callable+Future)防止内存溢出风险
ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000)) ;
	public boolean createOrder3() {
		long start = System.currentTimeMillis() ;
		List<Future<String>> results = new ArrayList<>(3) ;
		results.add(pool.submit(() -> {
			return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ;
		})) ;
		results.add(pool.submit(() -> {
			return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ;
		})) ;
		results.add(pool.submit(() -> {
			return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1}) ;
		})) ;
		for (int i = 0, size = results.size(); i < size; i++) {
			try {
				System.out.println(results.get(i).get()) ;
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			}
		}
		System.out.println("线程池方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ;
		return true ;
	}

调用结果:

SpringBoot项目中异步调用接口方式知多少?

 

耗时和上一个基本一致,通过Future的方式有一个问题就是只能一个一个的取值,只有当前的返回数据了后才会继续往下执行。如果有其它的任务执行完,那没有轮到它也必须等待。

 

  1. CompletionService(异步任务与使用已完成任务的结果分离),submit提交任务,take获取已经完成的任务,不用按照submit的顺序获取结果。
public boolean createOrder4() {
		long start = System.currentTimeMillis() ;
		CompletionService<String> cs = new ExecutorCompletionService<>(pool) ;
		cs.submit(() -> {
			return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ;
		}) ;
		cs.submit(() -> {
			return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ;
		}) ;
		cs.submit(() -> {
			return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1}) ;
		}) ;
		for (int i = 2 ; i >=0; i--) {
			try {
				System.out.println(cs.take().get()) ;
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			}
		}
		System.out.println("CompletionService方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ;
		return true ;
	}

调用结果:

SpringBoot项目中异步调用接口方式知多少?

 

通过CompletionService方式不管任务添加的顺序是什么,只要通过take方法就能获取执行完的结果,如果没有任务执行完,take方法会阻塞。

 

  1. CompletableFuture(异步任务编排),JDK1.8
public boolean createOrder5() {
		long start = System.currentTimeMillis() ;
		CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {
			return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ;
		}) ;
		
		CompletableFuture<String> storageFuture = CompletableFuture.supplyAsync(() -> {
			return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ;
		}) ;
		
		CompletableFuture<String> discountFuture = CompletableFuture.supplyAsync(() -> {
			return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1});
		}) ;
		CompletableFuture<List<String>> result = CompletableFuture
				.allOf(userFuture, storageFuture, discountFuture)
				.thenApply((Void) -> {
					List<String> datas = new ArrayList<>() ;
					try {
						datas.add(userFuture.get()) ;
						datas.add(storageFuture.get()) ;
						datas.add(discountFuture.get()) ;
					} catch (InterruptedException | ExecutionException e) {
						e.printStackTrace();
					}
					return datas ;
				}).exceptionally(e -> {
					e.printStackTrace() ;
					return null ;
				}) ;
		try {
			System.out.println(result.get()) ;
		} catch (InterruptedException | ExecutionException e1) {
			e1.printStackTrace();
		}
		System.out.println("CompletableFuture方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ;
		return true ;
	}

调用结果:

SpringBoot项目中异步调用接口方式知多少?

 

CompletableFuture提供了非常强大的异步编程方法,可同步,可异步,可编排任务执行,异步通过回调的方式执行。该对象很多的一些方法与前端JavaScript中的Promise对象有点相像。

完毕!!!

给个关注+转发呗,谢谢

 

SpringBoot项目中异步调用接口方式知多少?

 

SpringBoot项目中异步调用接口方式知多少?

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot,你可以使用异步调用来提高应用程序的性能和吞吐量。Spring Boot支持两种异步方式:使用Callable和DeferredResult。 使用Callable: 1. 在Controller定义一个方法,并使用@Async注解标记该方法。 ```java @RestController public class MyController { @Autowired MyService myService; @GetMapping("/async") public Callable<String> async() { return () -> myService.doSomething(); } } ``` 2. 在Service层定义一个异步方法,并使用@Async注解标记该方法。 ```java @Service public class MyService { @Async public String doSomething() { // 执行耗时操作 return "result"; } } ``` 使用DeferredResult: 1. 在Controller定义一个方法,并使用DeferredResult作为返回值。 ```java @RestController public class MyController { @Autowired MyService myService; @GetMapping("/async") public DeferredResult<String> async() { DeferredResult<String> deferredResult = new DeferredResult<>(); CompletableFuture.supplyAsync(() -> myService.doSomething()) .whenCompleteAsync((result, throwable) -> deferredResult.setResult(result)); return deferredResult; } } ``` 2. 在Service层定义一个异步方法,并使用CompletableFuture异步执行该方法。 ```java @Service public class MyService { public String doSomething() { // 执行耗时操作 return "result"; } } ``` 注意事项: 1. 需要在Spring Boot应用程序的启动类上添加@EnableAsync注解来启用异步调用。 ```java @SpringBootApplication @EnableAsync public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } ``` 2. 异步调用的返回值应该是Future或其子类,例如Callable和DeferredResult。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值