异步处理REST服务

异步处理REST服务,提高吞吐量


在这里插入图片描述
一个http请求进来tomcat获取其他中间件会有一个线程来处理http请求 ,然后给出一个httpx响应,这是同步处理的方式,
但我们知道像tomcat或中间件这种会有线程数量的限制,当线程数量到达一定程度后,再有请求进来tomcat就无法处理。

对于异步处理,当一个http请求进来以后,tomcat的主线程来调取一个副线程来处理业务逻辑,当副线程处理完后,主线程再返回http响应。在副线程处理业务逻辑中,主线程被空闲处理可以继续处理其他http请求

使用Runnable 异步处理REST服务

先看同步处理

在这里插入图片描述

package com.whale.web.async;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AsyncComtroller{

    private Logger logger = LoggerFactory.getLogger(getClass());


    @RequestMapping("/order")
    public String order() throws InterruptedException {
        //同步处理的方式 
        logger.info("主线程开始");
        Thread.sleep(1000);
        logger.info("主线程返回");
        return "success";
    }
}

访问 http://127.0.0.1:8080/order

在这里插入图片描述

异步处理


    @RequestMapping("/order")
    public Callable<String>  order() throws InterruptedException {
        logger.info("主线程开始");

        Callable<String> result = new Callable<String>() {
            @Override
            public String call() throws Exception {
                logger.info("副线程开始");
                Thread.sleep(1000);
                logger.info("副线程返回");
                return "success";
            }
        };
        logger.info("主线程返回");
        return result;
    }

访问 http://127.0.0.1:8080/order
在这里插入图片描述
可见副线程在处理真正的业务逻辑时,主线程仍能接受请求 ,
这就是异步处理的优势

使用DeferredResult异步处理REST服务

Runnable这种方法的缺点,副线程需要主线程调用
但有些企业级开发中主线程和副线程在两个服务器中
以下单举例
在这个例子里面,接收下单请求的应用1和处理下单逻辑的应用2并不是一台服务器而是两台服务器,
http下单请求发送到应用1上以后,线程1会将这个请求放到消息队列里面,然后另一台服务器上的应用2来监听这个详细队列
当监听到消息队列里面有下单请求时,由他开始处理下单逻辑,当他处理完下单逻辑,再把这个结果放到消息队列里面。
然后应用1的线程2来监听这消息队列,当消息队列里有下单处理结果这个消息类型以后,他会根据这个消息的结果来返回一个http响应。
在这个场景里面线程1和线程2是完全隔离的。这种情况下就不能用runnable了
得有DeferredResult
在这里插入图片描述

线程1接收请求,线程2返回响应,这两个线程独立存在互相不能调用

怎么处理这种情况
我们不开发应用2也不做消息队列,太复杂了

  1. 我们用一个对象来模拟这个消息队列
    在这个消息队列收到下单请求后会单独开启一个线程里面延迟1秒,然后在消息队列里面放一个订单完成的消息
    这是第一块代码

第二块代码是线程1的处理,我们controller中tomcat主线程的处理

第三块代码是监听器的代码,线程2它监听消息队列里面这个订单完成的消息,当订单完成的消息出现以后,它把结果返回回去

第四块 线程1和线程2之间DeferredResult 传递http请求及相应的处理

/**
 * @version V1.0
 * @Package com.whale.web.async
 * @Description: 模拟消息队列
 * @date 2019/2/13 0:25
 */
@Component
public class MockQueue implements Serializable {

    private String placeOrder;  //下单消息

    private String completeOrder; //订单完成后消息

    private Logger logger =  LoggerFactory.getLogger(getClass());

    public String getPlaceOrder(){
        return placeOrder;
    }

    public void setPlaceOrder(String placeOrder) throws InterruptedException {
        new Thread(()->{ //应该是由应用2处理,我们单开一个线程模拟

            logger.info("接到下单请求"+placeOrder);
            try {
                Thread.sleep(1000);//模拟下单处理逻辑消耗的时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.completeOrder = placeOrder;//当completeOrder 有值时说明下单完成
            logger.info("下单请求完成"+placeOrder);
            //this.placeOrder = placeOrder;

        }).start();

    }

    public String getCompleteOrder() {
        return completeOrder;
    }

    public void setCompleteOrder(String completeOrder) {
        this.completeOrder = completeOrder;
    }
}

  1. tomcat主线程的处理
@Autowired
    private MockQueue mockQueue;

    @Autowired
    private DeferredResultHolder deferredResultHolder;

    @RequestMapping("/order")
    public DeferredResult<String>  order() throws InterruptedException {
        logger.info("主线程开始");

        String orderNum = RandomStringUtils.randomNumeric(8);//生成订单号
        mockQueue.setPlaceOrder(orderNum);//订单号放到消息队列里 发送请求到消息队列

        DeferredResult<String> result = new DeferredResult<>();
        deferredResultHolder.getMap().put(orderNum,result);
        logger.info("主线程返回");
        return result;
    }
  1. 监听器代码 ,监听消息队列请求处理完成的消息后返回处理结果
/**
 * @version V1.0
 * @Package com.whale.web.async
 * @Description: 监听器
 * @date 2019/2/13 21:36
 */

// 实现 ApplicationListener<ContextRefreshedEvent>r接口  监听 spring 容器初始化完毕的事件
@Component
public class QueueListener implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private MockQueue mockQueue;

    @Autowired
    private DeferredResultHolder deferredResultHolder;

    private Logger logger =  LoggerFactory.getLogger(getClass());

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {

        new Thread(()->{  //单开线程否则会阻塞系统启动

            while (true){
                if(StringUtils.isNoneBlank(mockQueue.getCompleteOrder())){
                    String orderNum = mockQueue.getCompleteOrder();
                    logger.info("返回订单处理结果:"+orderNum);
                    deferredResultHolder.getMap().get(orderNum).setResult("place order success");
                    mockQueue.setCompleteOrder(null);//我们这个不是真的消息队列,需要再把他置为null,就不会循环处理

                }else {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }).start();

    }
}

  1. 构建两个线程之间的桥梁
    这个是第四块代码,是在线程1和线程2之间传递DeferredResult这个对象
@Component
public class DeferredResultHolder {

    /**
     * map:
     * key : 订单号 ;
     *  DeferredResult<String> 对应订单的处理结果
     */
    private Map<String,DeferredResult<String>> map  = new HashMap<>();

    public Map<String, DeferredResult<String>> getMap() {
        return map;
    }

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

在这里插入图片描述
一共有3个线程参与了整个请求处理过程
一个是nio-8080-exec-1 主线程1 接收http请求
一个是thread 40 模拟应用2 处理真正的业务逻辑
一个是thread 29 应用1的线程2将处理结果返回给前台
这3个线程互相隔离,通过消息队列来进行通讯

异步处理配置

ctrl+o 重新父类的方法,红框里的就是异步处理的4个配置方法
下面那个之前监听器的配置只适合同步处理
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值