springboot学习(五十七) springboot中使用Callable和DeferredResult异步处理请求


前言

Callable和DeferredResult可以用来进行异步请求处理。利用它们,我们可以异步生成返回值,在具体处理的过程中,我们直接在controller中返回相应的Callable或者DeferredResult,在这之后,servlet线程将被释放,可用于其他连接;DeferredResult另外会有线程来进行结果处理,并setResult。

一、异步请求处理的使用场景

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

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

二、Callable

涉及到较长时间的请求处理的话,比较好的方式是用异步调用,比如利用Callable返回结果。异步主要表现在,接收请求的servlet可以不用持续等待结果产生,而可以被释放去处理其他事情。当然,在调用者来看的话,其实还是表现在持续等待。这有利于服务端提供更大的并发处理量。

下面我们编写一个demo来测试,让这个请求等待三秒再返回

1.先定义一个ThreadPoolExecutor

package com.iscas.biz.config;

import com.iscas.biz.config.log.AccessLogInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.*;

/**
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2020/8/28 21:02
 * @since jdk1.8
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    @Qualifier("asyncExecutor")
    private ThreadPoolTaskExecutor asyncExecutor;

	......

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setTaskExecutor(asyncExecutor);
    }
}


2.定义通用返回实体

package com.iscas.templet.common;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * @Author: zhuquanwen
 * @Description:
 * @Date: 2017/12/25 16:41
 * @Modified:
 **/
@Data
@ToString(callSuper = true)
@Accessors(chain = true)
@AllArgsConstructor
public class ResponseEntity<T> implements Serializable {

    /**
     * http状态码
     */
    protected Integer status = 200;
    /**
     * 给予用户提示的信息
     */
    protected String message;

    /**
     * 出现错误的详细描述(调试)
     */
    protected String desc;

    /**
     * 异常堆栈信息
     * */
    protected String stackTrace;

    /**
     * 返回值
     */
    protected T value;

    /**
     * 访问URL
     */
    protected String requestURL;

    /**
     * 当前接口访问耗时
     * */
    protected long tookInMillis;

    /**
     * 过时的参数,未来会删除
     * */
    @Deprecated
    protected int total;

    public ResponseEntity(Integer status, String message) {
        super();
        this.status = status;
        this.message = message;
    }

    public ResponseEntity() {
        super();
        this.message = "操作成功";
    }

    public ResponseEntity(String message){
        super();
        this.message = message;
    }


}

package com.iscas.templet.common;

/**
 * Controller基础控制类
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2018/7/16
 * @since jdk1.8
 */
public class BaseController {
    /**
     * 获取返回模板
     * @version 1.0
     * @since jdk1.8
     * @date 2018/7/16
     * @return com.iscas.templet.common.ResponseEntity
     */
    public ResponseEntity getResponse() {
        return new ResponseEntity();
    }

    /**
     * 获取返回模板
     * @version 1.0
     * @since jdk1.8
     * @date 2018/7/16
     * @param tClass 返回的泛型Class
     * @return com.iscas.templet.common.ResponseEntity
     */
    public <T> ResponseEntity<T> getResponse(Class<T> tClass) {
        return new ResponseEntity<T>();
    }

}

3.编写测试的Restful接口

package com.iscas.biz.test.async;

import com.iscas.templet.common.BaseController;
import com.iscas.templet.common.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * 返回Callable 异步返回数据
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2021/9/9 20:41
 * @since jdk1.8
 */
@RestController
@RequestMapping("/callable/test")
public class CallableResultControllerTest extends BaseController {

    @GetMapping
    public Callable<ResponseEntity> callable() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return () -> new ResponseEntity<>().setValue("This is callale test");
    }
}

访问此URL,发现浏览器等待3秒后返回了

在这里插入图片描述
但controller的执行时间200ms,也就是说请求在其他线程执行的
在这里插入图片描述

二、DeferredResult

使用DeferredResult的流程:

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

下面我们设定一个场景通过DerfferredResult来实现:
浏览器向web服务发起请求,该请求需要等到RocketMq给web服务推送数据,如果推送了数据,才会向浏览器返回数据;
如果10秒内RocketMQ未给web服务推送数据,则返回超时。

1.定义一个通用处理DeferredResult的工具类

package com.iscas.base.biz.service.common;

import com.iscas.common.tools.core.string.StringRaiseUtils;
import com.iscas.templet.common.ResponseEntity;
import com.iscas.templet.exception.BaseRuntimeException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

/**
 * 处理DeferredResult
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2021/9/9 21:29
 * @since jdk1.8
 */
@Service
public class DeferredResultService {
    private Map<String, Consumer<ResponseEntity>> deferredResultMap = new ConcurrentHashMap<>();

    /**
     * 将请求标记与DeffredResult#setResult映射
     */
    public void process(String requestMark, DeferredResult<ResponseEntity> deferredResult) {
        //判断此requestMark对应的任务是否存在
        Optional.ofNullable(deferredResultMap)
                .filter(t -> !t.containsKey(requestMark))
                .orElseThrow(() -> new BaseRuntimeException(StringRaiseUtils.format("requestMark:{}对应的任务已存在", requestMark)));

        // 请求超时的回调函数
        deferredResult.onTimeout(() -> {
            //从等待处理的请求Map中移除
            deferredResultMap.remove(requestMark);
            deferredResult.setResult(new ResponseEntity().setStatus(HttpStatus.REQUEST_TIMEOUT.value()).setMessage("请求超时"));
        });

        //将setResult的消费者存入map
        deferredResultMap.putIfAbsent(requestMark, deferredResult::setResult);
    }


    /**
     * 设置处理结果
     */
    public void setResult(String requestMark, ResponseEntity responseEntity) {
        //如果deferredResultMap中存在这个requestMark,移除得到DeffredResult 并调用其setResult方法
        Optional.ofNullable(deferredResultMap.remove(requestMark))
                .ifPresent(c -> c.accept(responseEntity));
    }
}

2.编写测试的Restful接口

package com.iscas.biz.test.async;

import com.iscas.base.biz.service.common.DeferredResultService;
import com.iscas.templet.common.BaseController;
import com.iscas.templet.common.ResponseEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;

/**
 * 测试DeferredResult
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2021/9/9 21:02
 * @since jdk1.8
 */
@RestController
@RequestMapping("/deferred/result/test")
public class DeferredResultControllerTest extends BaseController {
    @Autowired
    private DeferredResultService deferredResultService;


    /**
     * 模拟一个请求,携带requestMark,并等待RocketMQ的结果
     * */
    @GetMapping("/{requestMark}")
    public DeferredResult<ResponseEntity> request1(@PathVariable String requestMark) {
        DeferredResult<ResponseEntity> deferredResult = new DeferredResult<>(10000L);
        deferredResultService.process(requestMark, deferredResult);
        //直接返回了,这时这个线程释放了,等待rocketmq推送结果,再返回数据给前端
        return deferredResult;
    }

    /**
     * 模拟rocketmq 推送数据
     * */
    @GetMapping("/push/{requestMark}")
    public ResponseEntity push(@PathVariable String requestMark) {
        deferredResultService.setResult(requestMark, new ResponseEntity().setMessage("假设这是rocketmq推送的数据"));
        return getResponse();
    }

}

在发送了/deferred/result/test/222请求,过了10秒超时后(模拟10s内rocketmq未返回数据):
在这里插入图片描述
在发送了/deferred/result/test/222请求,10秒内发送了/deferred/result/test/push/222请求(模拟10s内rocketmq返回了数据):
在这里插入图片描述

总结

和Callable一样,DeferredResult也是为了支持异步调用。两者的主要差异,DeferredResult需要自己来处理结果setResult,而Callable自动调额外的线程池做处理。总体来说,Callable的话更为简单,同样的也是因为简单,灵活性不够;相对地,DeferredResult更为复杂一些,但是又极大的灵活性。在可以用Callable的时候,直接用Callable;而遇到Callable没法解决的场景的时候,需要使用DeferredResult。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot处理异步请求和响应有多种方式,下面介绍一种常用的方法: 1. 使用`@Async`注解实现异步处理:在方法上添加`@Async`注解,使得该方法在调用时会在新的线程异步执行。 ```java @RestController public class MyController { @Autowired private MyService myService; @GetMapping("/async") public Callable<ResponseEntity<String>> asyncRequest() { return () -> { // 异步处理逻辑 String result = myService.processAsync(); return ResponseEntity.ok(result); }; } } @Service public class MyService { @Async public String processAsync() { // 异步处理逻辑 return "Async response"; } } ``` 在上面的示例,`MyController`类的`asyncRequest()`方法使用了`Callable`作为返回类型,这是因为Spring Boot支持将异步处理结果封装为`Callable`对象,以便在异步处理完成后返回给客户端。 2. 使用`DeferredResult`实现异步响应:`DeferredResult`是Spring提供的一个用于异步处理和响应的类。 ```java @RestController public class MyController { @Autowired private MyService myService; private DeferredResult<ResponseEntity<String>> deferredResult = new DeferredResult<>(); @GetMapping("/async") public DeferredResult<ResponseEntity<String>> asyncRequest() { // 设置超时处理 deferredResult.setTimeout(5000); deferredResult.onTimeout(() -> { deferredResult.setErrorResult(ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body("Request timeout")); }); // 异步处理逻辑 myService.processAsync(deferredResult); return deferredResult; } } @Service public class MyService { @Async public void processAsync(DeferredResult<ResponseEntity<String>> deferredResult) { // 异步处理逻辑 String result = "Async response"; deferredResult.setResult(ResponseEntity.ok(result)); } } ``` 在上面的示例,`MyController`类的`asyncRequest()`方法返回了一个`DeferredResult`对象,它会在异步处理完成后被设置为响应结果。 以上是两种常用的处理Spring Boot异步请求和响应的方式,你可以根据具体需求选择适合的方式来实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值