欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

课程Spring注解驱动学习笔记(十五)异步请求

程序员文章站 2024-03-19 16:34:04
...

异步处理

在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。

如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。即便是像Spring、Struts这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在Servlet之上的。为了解决这样的问题,Servlet 3.0引入了异步处理,然后在Servlet 3.1中又引入了非阻塞IO来进一步增强异步处理的性能。

一般请求

课程Spring注解驱动学习笔记(十五)异步请求

异步请求

课程Spring注解驱动学习笔记(十五)异步请求

 

添加HelloAsyncServlet

@WebServlet(value="/async",asyncSupported=true)
public class HelloAsyncServlet extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//1、支持异步处理asyncSupported=true
		//2、开启异步模式
		System.out.println("主线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
		AsyncContext startAsync = req.startAsync();
		//3、业务逻辑进行异步处理;开始异步处理
		startAsync.start(new Runnable() {
			@Override
			public void run() {
				try {
					System.out.println("副线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
					sayHello();
					startAsync.complete();
					//获取到异步上下文
					AsyncContext asyncContext = req.getAsyncContext();
					//4、获取响应
					ServletResponse response = asyncContext.getResponse();
					response.getWriter().write("hello async...");
					System.out.println("副线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
				} catch (Exception e) {
				}
			}
		});		
		System.out.println("主线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
	}

        //模拟很耗时间的业务逻辑
	public void sayHello() throws Exception{
		System.out.println(Thread.currentThread()+" processing...");
		Thread.sleep(3000);
	}
}

启动项目访问http://localhost:8081/servlet3.0/async

主线程开始。。。Thread[http-nio-8081-exec-1,5,main]==>1586686632678
主线程结束。。。Thread[http-nio-8081-exec-1,5,main]==>1586686632678
副线程开始。。。Thread[http-nio-8081-exec-2,5,main]==>1586686635361
Thread[http-nio-8081-exec-2,5,main] processing...
副线程结束。。。Thread[http-nio-8081-exec-2,5,main]==>1586686638361

 

SpringMVC异步处理

新建AsyncController

@Controller
public class AsyncController {
	
	/**
	 * @return
	 */
	@ResponseBody
	@RequestMapping("/async01")
	public Callable<String> async01(){
		System.out.println("主线程开始..."+Thread.currentThread()+"==>"+System.currentTimeMillis());
		
		Callable<String> callable = new Callable<String>() {
			@Override
			public String call() throws Exception {
				System.out.println("副线程开始..."+Thread.currentThread()+"==>"+System.currentTimeMillis());
				Thread.sleep(2000);
				System.out.println("副线程开始..."+Thread.currentThread()+"==>"+System.currentTimeMillis());
				return "Callable<String> async01()";
			}
		};
		
		System.out.println("主线程结束..."+Thread.currentThread()+"==>"+System.currentTimeMillis());
		return callable;
	}

}

课程Spring注解驱动学习笔记(十五)异步请求

原理

1、控制器返回Callable
2、Spring异步处理,将Callable 提交到 TaskExecutor 使用一个隔离的线程进行执行
3、DispatcherServlet和所有的Filter退出web容器的线程,但是response 保持打开状态;
4、Callable返回结果,SpringMVC将请求重新派发给容器,恢复之前的处理;
5、根据Callable返回的结果。SpringMVC继续进行视图渲染流程等(从收请求-视图渲染)。

    preHandle.../springmvc-annotation/async01
    主线程开始...Thread[http-bio-8081-exec-3,5,main]==>1513932494700
    主线程结束...Thread[http-bio-8081-exec-3,5,main]==>1513932494700
    =========DispatcherServlet及所有的Filter退出线程============================
        
    ================等待Callable执行==========
    副线程开始...Thread[MvcAsync1,5,main]==>1513932494707
    副线程开始...Thread[MvcAsync1,5,main]==>1513932496708
    ================Callable执行完成==========
        
    ================再次收到之前重发过来的请求========
    preHandle.../springmvc-annotation/async01
    postHandle...(Callable的之前的返回值就是目标方法的返回值)
    afterCompletion...
       
        

异步的拦截器

1)原生API的AsyncListener
2)SpringMVC:实现AsyncHandlerInterceptor

 

SpringMVC异步请求实际使用场景

一个分布式应用需要处理一个创建订单的业务需要调用另一个服务,通过消息中间件传递消息

课程Spring注解驱动学习笔记(十五)异步请求

新建一个队列,模拟消息处理

public class DeferredResultQueue {
	
	private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedQueue<DeferredResult<Object>>();
	
	public static void save(DeferredResult<Object> deferredResult){
		queue.add(deferredResult);
	}
	
	public static DeferredResult<Object> get( ){
		return queue.poll();
	}

}

新建两个方法用于模拟消息发送和处理

@Controller
public class AsyncController {
	
	@ResponseBody
	@RequestMapping("/createOrder")
	public DeferredResult<Object> createOrder(){
		DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail...");
			
		DeferredResultQueue.save(deferredResult);
		
		return deferredResult;
	}
	
	@ResponseBody
	@RequestMapping("/create")
	public String create(){
		//创建订单
		String order = UUID.randomUUID().toString();
		DeferredResult<Object> deferredResult = DeferredResultQueue.get();
		deferredResult.setResult(order);
		return "success===>"+order;
	}
}

一个服务产生订单请求放入队列中,另一个请求进行产生订单服务

 

我们先访问

http://localhost:8081/springmvc-annotation/createOrder

请求订单

再访问

http://localhost:8081/springmvc-annotation/create

获取产生的订单

课程Spring注解驱动学习笔记(十五)异步请求

课程Spring注解驱动学习笔记(十五)异步请求