(05)使用DeferredResult多线程异步处理请求

Posted javasl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(05)使用DeferredResult多线程异步处理请求相关的知识,希望对你有一定的参考价值。

  通常处理HTTP请求时使用同步处理的方式,但有时根据业务场景和性能要求异步处理可能更合适。简单说下概念。

  同步处理:一个HTTP请求进入一个主线程,主线程处理完后给出一个HTTP响应。

  异步处理:一个HTTP请求进入一个主线程,主线程调用一个副线程,副线程处理业务逻辑,当副线程处理完后,主线程把结果返回给给客户端。在副线程处理逻辑的同时,主线程可以空闲出来处理其他请求。因为服务器同时处理的线程数量有限,所以异步处理提升了服务器的吞吐量。

  异步处理请求有Runnable和DeferredResult两种方式,下面举例说一下。

  1、Runnable异步处理REST服务

@RestController
public class AsyncController {    
    
    private Logger logger=LoggerFactory.getLogger(getClass());
    
    @RequestMapping("/order")
    public Callable<String> order(){
        logger.info("主线程开始");
        Callable<String> result=new Callable<String>() {
            @Override 
            public String call() throws Exception {
                logger.info("副线程开始");
                Thread.sleep(5000);
                logger.info("副线程结束");
                return "success"; 
            }
        };
        logger.info("主线程返回");
        return result;
    }
}

  启动服务,浏览器访问http://localhost/order,页面5秒后返回success,查看日志如下 :

  技术图片

  日志中显示了两个线程,主线程返回后过了5秒副线程结束。

  2、DeferredResult异步处理Rest服务

  通过上面的例子可以看到使用Runnable异步处理,副线程由主线程调起。这样有些业务场景不能满足,所以使用DeferredResult。举例如下:

  技术图片

  接受下单请求和处理下单逻辑的应用不在一台服务器上。线程1接受HTTP请求,并把该请求放到一个消息队列里。应用2监听消息队列,当监听到消息队列里有下单请求后处理下单逻辑,处理完后把结果再放到这个消息队列里。应用1中线程2监听消息队列,当监听到订单处理结果的消息后,会根据这个消息的结果返回一个HTTP响应给客户端。在这个场景中,线程1和线程2是完全隔离的,谁也不知道谁的存在。

  模拟消息队列:MockQueue.java,setPlaceOrder方法表示队列中处理下单请求,创建一个Thread模拟其它应用环境。

@Component
public class MockQueue {
    
    private Logger logger=LoggerFactory.getLogger(getClass());
    
    private String placeOrder;
    private String completeOrder;
    
    public String getPlaceOrder() {
        return placeOrder;
    }
    public void setPlaceOrder(String placeOrder){
        new Thread(()->{
            logger.info("接到下单请求:"+placeOrder);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.completeOrder = placeOrder;
            logger.info("下单请求处理完毕:"+placeOrder);
        }).start();
    }
    public String getCompleteOrder() {
        return completeOrder;
    }
    public void setCompleteOrder(String completeOrder) {
        this.completeOrder = completeOrder;
    }
}

  在线程1和线程2之间传递DeferredResult对象:DeferredResultHolder.java,Map中key是订单号,value是处理结果。

@Component
public class DeferredResultHolder {

    private Map<String,DeferredResult<String>> map=new HashMap<String,DeferredResult<String>>();

    public Map<String, DeferredResult<String>> getMap() {
        return map;
    }
    public void setMap(Map<String, DeferredResult<String>> map) {
        this.map = map;
    }
}

  处理请求:AsyncController.java

@RestController
public class AsyncController {    
    
    private Logger logger=LoggerFactory.getLogger(getClass());
    
    @Autowired
    private MockQueue mockQueue;
    @Autowired
    private DeferredResultHolder deferredResultHolder;
    
    @RequestMapping("/order")
    public DeferredResult<String> order(){
        logger.info("主线程开始");
        String orderNumber=RandomStringUtils.randomNumeric(8);
        mockQueue.setPlaceOrder(orderNumber);
        DeferredResult<String> result=new DeferredResult<>();
        deferredResultHolder.getMap().put(orderNumber, result);
        logger.info("主线程返回");
        return result;
    } 
}

  模拟线程2的代码,监听器:QueueListener.java。ContextRefreshedEvent事件是整个Spring容器初始化完毕的事件。

@Component
public class QueueListener implements ApplicationListener<ContextRefreshedEvent> {
    
    private Logger logger=LoggerFactory.getLogger(getClass());
    
    @Autowired
    private MockQueue mockQueue;
    @Autowired
    private DeferredResultHolder deferredResultHolder;
    
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        new Thread(()->{//开启一个线程,否则Spring容器阻塞,无法启动
            while(true) {
                if(StringUtils.isNotBlank(mockQueue.getCompleteOrder())) {
                    String orderNumber=mockQueue.getCompleteOrder();
                    logger.info("返回订单处理结果:"+orderNumber);
                    deferredResultHolder.getMap().get(orderNumber).setResult("place order success");
                    mockQueue.setCompleteOrder(null);
                }else {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

  启动容器:请求3次http://localhost/order,页面输出place order success,打印日志如下:

  技术图片

以上是关于(05)使用DeferredResult多线程异步处理请求的主要内容,如果未能解决你的问题,请参考以下文章

使用DeferredResult实现异步处理REST服务示例

Spring - DeferredResult 异步返回实现

理解Callable 和 Spring DeferredResult

Spring DeferredResult 异步请求

DeferredResult使用方式和场景

DeferredResult 如何实现长轮询?