环境:SpringBoot2.7.12
1. 概述
在现代的互联网应用中,随着用户数量的不断增加和业务复杂性的提升,并发问题成为了开发中面临的重大挑战。传统的同步请求接口往往无法满足高并发场景的需要,不仅会阻塞调用线程,影响系统的响应性能,而且还可能导致线程资源的浪费。为了解决这些问题,异步请求接口逐渐成为了开发者的首选。
在SpringBoot框架中,异步请求接口的创建和使用非常方便,能够让你轻松解决并发问题,提高系统的可维护性和响应性能。本文将介绍如何快速掌握SpringBoot异步请求接口,以轻松解决并发问题。
2. 异步请求接口优势
异步请求接口相比于传统同步请求具有以下优势:
总之,异步请求接口具有上述优势,尤其是在高并发场景下,能够提高系统的性能和可用性,是解决并发问题的有效方法之一。
3. 应用场景
异步请求接口可以应用于以下场景:
总之,异步请求接口适用于那些需要避免阻塞、提高系统响应性能、处理耗时操作和实时数据处理等场景中,能够提高系统的并发性能和资源利用率,减少系统瓶颈的出现。
接下来我们进入正文,在Spring环境下如何将我们的接口异步化。
4. 实战异步接口
Spring MVC 广泛集成了 Servlet 3.0 异步请求处理功能:
4.1 DeferredResult
一旦在 Servlet 容器中启用异步请求处理功能,控制器方法就可以用 DeferredResult 封装任何受支持的控制器方法返回值,如下例所示:
@GetMapping("/deferred")@ResponseBodypublic DeferredResult<Map<String, Object>> deferred(){ long start = System.currentTimeMillis() ; System.out.printf("%s - 开始时间:%d%n", Thread.currentThread().getName(), start) ; DeferredResult<Map<String, Object>> deferredResult = new DeferredResult<>(); // 为了演示方便直观,这里直接创建线程 new Thread(() -> { try { // 这里模拟耗时操作 TimeUnit.SECONDS.sleep(3) ; // 将执行结果保存 Map<String, Object> result = new HashMap<>() ; result.put("code", 1) ; result.put("data", "你的业务数据") ; deferredResult.setResult(result) ; } catch (InterruptedException e) {} }).start() ; long end = System.currentTimeMillis() ; System.out.printf("%s - 结束时间:%d%n", Thread.currentThread().getName(), end) ; System.out.printf("总耗时:%d毫秒%n", (end - start)) ; return deferredResult ;}
控制台输出结果:
2023-10-19 14:25:30.321 INFO 3884 --- [nio-8808-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 mshttp-nio-8808-exec-1 - 开始时间:1697696730335http-nio-8808-exec-1 - 结束时间:1697696730335总耗时:0毫秒
从结果看出,处理请求的tomcat线程几乎没有占用时间,线程被快速的释放,这样就可以去处理其它的连接请求,整个系统的吞吐量就的到了明显的提升。
Controller可以从不同的线程异步生成返回值。
4.2 Callable
Controller可以用 java.util.concurrent.Callable 封装任何受支持的返回值,如下例所示:
@GetMapping("/callable")public Callable<Map<String, Object>> callable() { long start = System.currentTimeMillis() ; System.out.printf("%s - 开始时间:%d%n", Thread.currentThread().getName(), start) ; Callable<Map<String, Object>> callable = new Callable<Map<String, Object>>() { public Map<String, Object> call() throws Exception { Map<String, Object> result = new HashMap<>() ; try { // 这里模拟耗时操作 TimeUnit.SECONDS.sleep(3) ; // 将执行结果保存 result.put("code", 1) ; result.put("data", "你的业务数据") ; } catch (InterruptedException e) {} return result ; } } ; long end = System.currentTimeMillis() ; System.out.printf("%s - 结束时间:%d%n", Thread.currentThread().getName(), end) ; System.out.printf("总耗时:%d毫秒%n", (end - start)) ; return callable ;}
控制台输出结果:
http-nio-8808-exec-2 - 开始时间:1697697345385http-nio-8808-exec-2 - 结束时间:1697697345386总耗时:1毫秒
执行结果与上面一样。
注意:这里Callable中的代码执行是在系统默认的一个TaskExecutor线程池中运行,我们可以通过配置自己的TaskExecutor来执行。如下:
@Beanpublic ThreadPoolTaskExecutor myAsyncTaskExecutor() { // 配置ThreadPoolTaskExecutor相关参数,比如核心线程数等 return ... ;}
4.3 ResponseBodyEmitter
你可以将 DeferredResult 和 Callable 用于单个异步返回值。如果要生成多个异步值并将其写入响应,该怎么办?本节将介绍如何做到这一点。
ResponseBodyEmitter 的返回值生成一个对象流,其中每个对象都会被 HttpMessageConverter 序列化并写入响应,如下例所示:
// 这里应该保存到一个集合中private ResponseBodyEmitter emitter ;@GetMapping("/emitter")public ResponseBodyEmitter emitter() throws Exception { ResponseBodyEmitter bodyEmitter = new ResponseBodyEmitter(-1L); this.emitter = bodyEmitter ; return bodyEmitter;}// 可以不断调用该接口进行消息的发送@GetMapping("/sender")public void sender() throws Exception { this.emitter.send(System.currentTimeMillis()) ;}// 调用该j接口后请求结束@GetMapping("/complete")public void complete() throws Exception { this.emitter.complete() ;}
当访问/emitter接口时,浏览器会一直转圈,一直等待。只有调用了/complete接口后请求内容才会被发送到客户端并结束请求。
4.4 StreamingResponseBody
有时,绕过消息转换并直接流式传输到响应的 OutputStream(例如,文件下载)非常有用。为此,可以使用 StreamingResponseBody 返回值类型,如下例所示:
@GetMapping("/stream")public ResponseEntity<StreamingResponseBody> stream() { long start = System.currentTimeMillis() ; System.out.printf("%s - 开始时间:%d%n", Thread.currentThread().getName(), start) ; // 内部执行还是用的系统内部的线程池 StreamingResponseBody stream = new StreamingResponseBody() { @Override public void writeTo(OutputStream outputStream) throws IOException { outputStream.write(String.valueOf("当前时间: " + System.currentTimeMillis() + "<br/>").getBytes()) ; try { TimeUnit.SECONDS.sleep(1) ; } catch (InterruptedException e) {} outputStream.write(String.valueOf("当前时间: " + System.currentTimeMillis() + "<br/>").getBytes()) ; try { TimeUnit.SECONDS.sleep(1) ; } catch (InterruptedException e) {} outputStream.write(String.valueOf("当前时间: " + System.currentTimeMillis() + "<br/>").getBytes()) ; } }; MultiValueMap<String, String> headers = new HttpHeaders() ; headers.add("Content-Type", "text/html;charset=UTF-8") ; ResponseEntity<StreamingResponseBody> response = new ResponseEntity<StreamingResponseBody>(stream, headers , HttpStatus.OK) ; long end = System.currentTimeMillis() ; System.out.printf("%s - 结束时间:%d%n", Thread.currentThread().getName(), end) ; System.out.printf("总耗时:%d毫秒%n", (end - start)) ; return response ;}
控制台输出:
http-nio-8808-exec-1 - 开始时间:1697700256912http-nio-8808-exec-1 - 结束时间:1697700256915总耗时:3毫秒
tomcat线程非常短的时间内释放,这样就可以处理更多的请求,提升系统整体的吞吐量。这种最适合文件下载。
浏览器输出:
4.5 SSE
该方式请阅读《实时数据推送并非只有WebSocket一种选择》详细介绍了如何使用。
4.6 基于反应式
Spring MVC 支持在控制器中使用反应式客户端库。这包括 spring-webflux 中的 WebClient 以及 Spring Data 反应式数据存储库等其他库。在这种情况下,从控制器方法中返回反应类型是很方便的。如下例所示:
@GetMapping("/mono")public Mono<Map<String, Object>> mono() { long start = System.currentTimeMillis() ; System.out.printf("%s - 开始时间:%d%n", Thread.currentThread().getName(), start) ; Mono<Map<String, Object>> mono = Mono.defer(() -> { try { TimeUnit.SECONDS.sleep(3) ; } catch (InterruptedException e) {} Map<String, Object> result = new HashMap<>() ; result.put("code", 1) ; result.put("data", "你的业务数据") ; return Mono.just(result) ; }) ; long end = System.currentTimeMillis() ; System.out.printf("%s - 结束时间:%d%n", Thread.currentThread().getName(), end) ; System.out.printf("总耗时:%d毫秒%n", (end - start)) ; return mono ;}
控制台输出:
http-nio-8808-exec-2 - 开始时间:1697700686250http-nio-8808-exec-2 - 结束时间:1697700686251总耗时:1毫秒
以上就是Spring中异步请求接口的实现方式。
异步请求接口是解决并发问题的有效方法之一,特别是在高并发、耗时操作、实时数据处理等场景中具有显著优势。通过异步请求,系统能够避免阻塞线程,提高系统的响应性能和资源利用率。
本文链接:http://www.28at.com/showinfo-26-14319-0.html快速掌握Spring异步请求接口,轻松解决并发问题
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 图形编辑器开发:实现自定义规则输入框组件
下一篇: 前端项目重构的深度思考和复盘