1. 程式人生 > >Servlet3.0——非同步請求處理

Servlet3.0——非同步請求處理

在Servlet 3.0之前,Servlet採用Thread-Per-Request的方式處理請求,即每一次Http請求都由某一個執行緒從頭到尾負責處理,當過來一個請求之後,會從tomcat的執行緒池中拿出一個執行緒去處理這個請求,處理完成之後再將該執行緒歸還到執行緒池。但是執行緒池的數量是有限的,如果一個請求需要進行IO操作,比如訪問資料庫(或者呼叫第三方服務介面等),那麼其所對應的執行緒將同步地等待IO操作完成, 而IO操作是非常慢的,所以此時的執行緒並不能及時地釋放回執行緒池以供後續使用,在併發量越來越大的情況下,這將帶來嚴重的效能問題。即便是像Spring、Struts這樣的高層框架也脫離不了這樣的桎梏,因為他們都是建立在Servlet之上的。為了解決這樣的問題,Servlet 3.0引入了非同步處理,在Servlet 3.1中又引入了非阻塞IO來進一步增強非同步處理的效能。

1、Servlet3.0中請求的非同步處理:

@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); } }
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); } }

asyncSupported=true開啟servlet的非同步處理功能

AsyncContext startAsync = req.startAsync();獲取非同步上下文,也可以使用AsyncContext asyncContext = req.getAsyncContext();的方式獲取非同步上下文

startAsync.complete();非同步請求完成通知

ServletResponse response = asyncContext.getResponse();獲取響應,這樣在非同步請求中也可以給前端響應

這種情況下過來的請求先從tomcat的執行緒池中獲取一個執行緒處理該請求,在該執行緒中又開啟了一個非同步執行緒去處理一些耗時的操作,這樣就可以使tomcat的執行緒得以快速釋放,就能夠處理更多的併發請求了,但是這樣情況下的處理非同步請求的執行緒的管理也是由tomcat負責的,在SpringMVC中可以由SpringMVC負責管理。

2、SpringMVC的非同步請求處理

①返回Callable<T>

@Controller
public class AsyncController {
	
	@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;
	}

}	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;
	}

}	

如果控制器返回Callable物件,則SpringMVC會將該請求進行非同步處理——將Callable提交到TaskExecutor(由SpringMVC管理)中使用一個隔離的執行緒進行處理,同時DispatcherServlet和所有的Filter退出web容器的執行緒(可以繼續處理其他的HTTP請求),但是response依然保持開啟狀態,用以非同步的給瀏覽器響應,主執行緒處理完成,等待Callable執行完成。當Callable返回結果時,SpringMVC會將請求重新派發給容器(相當於重新發一個同樣地請求給web容器),恢復之前的請求處理(此時已經拿到了處理結果——Callable的返回值,不會再執行目標方法),SpringMVC進入檢視渲染等操作,

    非同步的攔截器:
1)、原生API的AsyncListener

2)、SpringMVC:實現AsyncHandlerInterceptor

②返回DeferredResult<T>

controller:

@Controller
public class HelloController {

	@Autowired
	DeferredResultService deferredResultService;

	@ResponseBody
	@RequestMapping("/createOrder")
	public DeferredResult<Object> createOrder() {
		DeferredResult<Object> defer = new DeferredResult<Object>((long) 10000, "create fail...");
		deferredResultService.save(defer);
		return defer;
	}

	@ResponseBody
	@RequestMapping("/create")
	public String create() {
		String string = UUID.randomUUID().toString();
		DeferredResult<Object> deferredResult = deferredResultService.get();
		deferredResult.setResult(string);
		return "success" + string;
	}

}(long) 10000, "create fail...");
		deferredResultService.save(defer);
		return defer;
	}

	@ResponseBody
	@RequestMapping("/create")
	public String create() {
		String string = UUID.randomUUID().toString();
		DeferredResult<Object> deferredResult = deferredResultService.get();
		deferredResult.setResult(string);
		return "success" + string;
	}

}

DeferredResultService:

@Service
public class DeferredResultService {

	/**
	 * 佇列
	 */
	private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedQueue<DeferredResult<Object>>();

	public void save(DeferredResult<Object> deferredResult) {
		queue.add(deferredResult);
	}

	public DeferredResult<Object> get() {
		return queue.poll();
	}
}

/createOrder請求中通過DeferredResultService的save方法往佇列(先進先出)裡面放了一個DeferredResult物件(此處為該物件設定了超時響應時間,也可以不設定),/create請求中從佇列裡面獲取到/createOrder中放入的DeferredResult物件,並呼叫其setResult()方法,效果是在請求/createOrder響應超時之前請求/create會導致/createOrder也得到響應,若在請求/createOrder超時之後請求/create則/createOrder請求得到響應失敗的響應,也就是說返回DeferredResult物件的請求的響應有兩種情況:

    1)、在超時時間內未呼叫DeferredResult物件的setResult()方法導致的響應超時的異常響應;

    2)、在超時時間內的任何一個時點呼叫DeferredResult物件的setResult()方法,都會使返回DeferredResult物件的請求立即響應;

總結:返回DeferredResult物件的請求的響應是在呼叫該物件的setResult()後就會立即響應或者是超時後的異常響應,原理應該是在new DeferredResult物件的時候即會建立一個執行緒,在該物件的setResult()方法中監聽該執行緒的執行情況並給出返回值。