异步处理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的处理,我们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;
}
}
- 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;
}
- 监听器代码 ,监听消息队列请求处理完成的消息后返回处理结果
/**
* @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和线程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个配置方法
下面那个之前监听器的配置只适合同步处理