Spring Boot 基于DeferredResult的异步服务

场景

假设我们现在要实现这样一个功能:浏览器要实时展示服务端计算出来的数据。
一种可能的实现是:浏览器频繁(例如定时1秒)向服务端发起请求以获得服务端数据。但定时请求并不能“实时”反应服务端的数据变化情况。
若定时周期为S,则数据延迟周期最大即为S。若想缩短数据延迟周期,则应使S尽量小,而S越小,浏览器向服务端发起请求的频率越高,又造成网络握手次数越多,影响了效率。因此,此场景应使用服务端实时推送技术。

这里说是推送,其实还是基于请求-响应机制,只不过发起的请求会在服务端挂起,直到请求超时或服务端有数据推送时才会做出响应,响应的时机完全由服务端控制。所以,整体效果看起来就像是服务端真的在“实时推送”一样。

可以利用DeferredResult来实现异步长连接的服务端实时推送。

为什么使用DeferredResult

  • API接口需要在指定时间内将异步操作的结果同步返回给前端时
  • Controller处理耗时任务,并且需要耗时任务的返回结果时

当一个请求到达API接口,如果该API接口的return返回值是DeferredResult,在没有超时或者DeferredResult对象设置setResult之前,接口不会返回,但是Servlet容器线程会结束,DeferredResult另起线程来进行结果处理(即这种操作提升了服务短时间的吞吐能力),并setResult,如此以来这个请求不会占用服务连接池太久,如果超时或设置setResult,接口会立即返回。

使用DeferredResult的流程:

  • 浏览器发起异步请求
  • 请求到达服务端被挂起(使用浏览器查看请求状态,此时为pending
  • 向浏览器进行响应,分为两种情况:
    • 调用DeferredResult.setResult(),请求被唤醒,返回结果
    • 超时,返回一个你设定的结果
  • 浏览得到响应,再次重复1,处理此次响应结果

给人一种异步处理业务,但是却同步返回的感觉。

核心代码

@RequestMapping("/call")
@ResponseBody
public DeferredResult<Object> call() { // 泛型Object表示返回结果的类型
    DeferredResult<Object> response = new DeferredResult<Object>( 
        10000, // 请求的超时时间 
        null); // 超时后响应的结果
    response.onCompletion(new Runnable() {

        @Override
        public void run() {
            // 请求处理完成后所做的一些工作
        }
    });
    // 设置响应结果
    // 调用此方法时立即向浏览器发出响应,未调用时请求被挂起
    response.setResult(new Object());
    return response;
}

Spring Boot 使用DeferredResult

在pom.xml中引入配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

建立Service层接口

public interface PiceaService {
    //无返回参数方法
    void task() throws Exception;
    //有返回参数方法
    String task2() throws Exception;
}

建立Service层实现

Service层接口与实现,跟其他正常同步请求一样,没有差别

@Service
public class PiceaServiceImpl implements PiceaService {

    @Override
    public void task() throws Exception {
        System.out.println("------------------------在看貂蝉,不要打扰--------------");
        Thread.sleep(1000);
    }

    @Override
    public String task2() throws Exception {
        int k = 1;
        System.out.println("------------------------在看鱼,不要打扰--------------");
        Thread.sleep(1000);
        return (String.valueOf(k));
    }
}

建立Contoller层方法

这是重点和核心,首先要定义一个线程池来处理异步的任务。然后就是编写异步的调用方法。

@RestController
public class PiceaServletContoller {

    @Autowired
    private PiceaService piceaService;

    @RequestMapping("/deferredresult")
    public DeferredResult<String> deferredResult() throws Exception {
        System.out.println("控制层执行线程: " + Thread.currentThread().getName());
        //超时
        DeferredResult<String> deferredResult = new DeferredResult<String>(10*1000L);
        deferredResult.onTimeout(new Runnable() {
            @Override
            public void run() {
                System.out.println("异步线程执行超时");
                deferredResult.setResult("线程执行超时");
            }
        });
        deferredResult.onCompletion(new Runnable() {
            @Override
            public void run() {
                System.out.println("异步执行完毕");
            }
        });
        FIXED_THREAD_POOL.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("异步执行线程: " + Thread.currentThread().getName());
                try {
                    String str = piceaService.task2();
                    Thread.sleep(1000);
                    deferredResult.setResult("这是【异步】的请求返回: " + str);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        return deferredResult;
    }

    /**
     * 线程池
     */
    public static ExecutorService FIXED_THREAD_POOL = Executors.newFixedThreadPool(10);
}

测试结果及方法

浏览器中访问http://localhost:2001/deferredresult测试结果。

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/27/spring-boot-asynchronous-service-based-on-deferredresult/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Spring Boot 基于DeferredResult的异步服务
场景 假设我们现在要实现这样一个功能:浏览器要实时展示服务端计算出来的数据。 一种可能的实现是:浏览器频繁(例如定时1秒)向服务端发起请求以获得服务端……
<<上一篇
下一篇>>
文章目录
关闭
目 录