Java Servlet線程中AsyncContext異步處理Http請求
AsyncContext
AsyncContext
是Servlet 3.0
使Servlet 線程不再需要一直阻塞,直到業(yè)務(wù)處理完畢才能再輸出響應(yīng),最后才結(jié)束該Servlet線程。在接收到請求之后,Servlet線程可以將耗時的操作委派給另一個線程來完成,自己在不生成響應(yīng)的情況下返回至容器。針對業(yè)務(wù)處理較耗時的情況,這將大大減少服務(wù)器資源的占用,并且提高并發(fā)處理速度
Servlet 3.0新增了異步處理,可以先釋放容器分配給請求的線程與相關(guān)資源,減輕系統(tǒng)負(fù)擔(dān),原先釋放了容器所分配線程的請求,其響應(yīng)將被延后,可以在處理完成(例如長時間運(yùn)算完成、所需資源已獲得)時再對客戶端進(jìn)行響應(yīng)。
在Servlet 3.0中,在ServletRequest
上提供了startAsync()
方法,該方法會根據(jù)請求的ServletRequest
和ServletResponse
創(chuàng)建AsyncContext
對象。
@Override public AsyncContext startAsync() { return startAsync(getRequest(),response.getResponse()); } @Override public AsyncContext startAsync(ServletRequest request, ServletResponse response) { if (!isAsyncSupported()) { IllegalStateException ise = new IllegalStateException(sm.getString("request.asyncNotSupported")); log.warn(sm.getString("coyoteRequest.noAsync", StringUtils.join(getNonAsyncClassNames())), ise); throw ise; } if (asyncContext == null) { asyncContext = new AsyncContextImpl(this); } asyncContext.setStarted(getContext(), request, response, request==getRequest() && response==getResponse().getResponse()); asyncContext.setTimeout(getConnector().getAsyncTimeout()); return asyncContext; }
請求調(diào)用startAsync
后Servlet
線程將會被釋放,請求交由其他線程去處理,如果業(yè)務(wù)線程沒有處理完,客戶端將不會收到響應(yīng),直到調(diào)用AsyncContext
的complete()
和dispatch(ServletContext context, String path)
方法為止,dispatch
方法會根據(jù)path
進(jìn)行重定向。AsyncContextImpl
大致代碼如下:
@Override public void complete() { if (log.isDebugEnabled()) { logDebug("complete "); } check(); request.getCoyoteRequest().action(ActionCode.ASYNC_COMPLETE, null); } @Override public void dispatch() { check(); String path; String cpath; ServletRequest servletRequest = getRequest(); if (servletRequest instanceof HttpServletRequest) { HttpServletRequest sr = (HttpServletRequest) servletRequest; path = sr.getRequestURI(); cpath = sr.getContextPath(); } else { path = request.getRequestURI(); cpath = request.getContextPath(); } if (cpath.length() > 1) { path = path.substring(cpath.length()); } if (!context.getDispatchersUseEncodedPaths()) { path = UDecoder.URLDecode(path, StandardCharsets.UTF_8); } dispatch(path); }
AsyncContext使用示例及測試
示例
設(shè)置Tomcat線程數(shù)為1
server:
port: 9099
servlet:
context-path: /server/v1
# 設(shè)置Tomcat線程數(shù)為1
tomcat:
min-spare-threads: 1
max-threads: 1
Controller
@RestController public class AsyncTestController { private final ScheduledExecutorService timeoutChecker = new ScheduledThreadPoolExecutor(1, threadFactory); private static boolean result = false; @PostMapping("/async") public void async(@RequestBody Request re1, HttpServletRequest request, HttpServletResponse response) { // 創(chuàng)建AsyncContext AsyncContext asyncContext = request.startAsync(request, response); String name = re1.getUsername(); // 設(shè)置處理超時時間2s asyncContext.setTimeout(2000L); // asyncContext監(jiān)聽 asyncContext.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent asyncEvent) throws IOException { } @Override public void onTimeout(AsyncEvent asyncEvent) throws IOException { asyncContext.getResponse().getWriter().print(name + ":timeout"); asyncContext.complete(); } @Override public void onError(AsyncEvent asyncEvent) throws IOException { } @Override public void onStartAsync(AsyncEvent asyncEvent) throws IOException { } }); // 定時處理業(yè)務(wù),處理成功后asyncContext.complete();完成異步請求 timeoutChecker.scheduleWithFixedDelay(() -> { try { if (result) { asyncContext.getResponse().getWriter().print(name); asyncContext.complete(); } } catch (IOException e) { e.printStackTrace(); } }, 0, 100L, TimeUnit.MILLISECONDS); } // 模擬業(yè)務(wù)處理完成 @PostMapping("/notify") public void notify(Boolean s) { result = s; } }
測試結(jié)果
- 測試指標(biāo)
并發(fā)5,兩個循環(huán)
- 測試結(jié)果
10條并發(fā)請求都能夠處理,并且處理的時間都是2s左右(因為設(shè)置的超時時間是2s),通過該測試結(jié)果可以看出,使用AsyncContext
可以在容器資源有限的情況下處理更多的請求,這在高并發(fā)場景下就比較有用了。
AsyncContext應(yīng)用場景
使用AsyncContext
實現(xiàn)的Http長輪詢在許多的中間件的信息同步場景中應(yīng)用廣泛,例如Nacos配置中心和Apache Shenyu網(wǎng)關(guān)。
背景
公司一個系統(tǒng)是Netty
實現(xiàn)的TCP協(xié)議的服務(wù),其中的一個業(yè)務(wù)是設(shè)備請求后臺接口查詢支付結(jié)果,接口的處理邏輯是收到請求后就將請求放到一個隊列中,然后由業(yè)務(wù)線程異步處理,當(dāng)收到支付結(jié)果完成后將響應(yīng)給客戶端支付結(jié)果,該接口的超時時間是2s,如果2s查不到支付結(jié)果就返回給客戶端查不到結(jié)果,客戶端收到該錯誤后重新發(fā)起查詢,直到客戶端的整個業(yè)務(wù)超時。
公司由于服務(wù)架構(gòu)調(diào)整,要將該系統(tǒng)改造成基于SpringBoot的Http協(xié)議接口,如果”支付結(jié)果查詢接口“不做機(jī)制的變更,就會導(dǎo)致每一次結(jié)果查詢都會阻塞等待隊列中查詢支付結(jié)果的查詢,因為支付是異步的,所以支付結(jié)果查詢會比較耗時,如果機(jī)制不改那么如果并發(fā)增大的話會導(dǎo)致服務(wù)器的處理請求線程全部被打滿,整個服務(wù)對于其他請求,其他業(yè)務(wù)都變得不可用了,這個結(jié)果是不可以接受的。
AsyncContext解決生產(chǎn)問題
基于示例中的demo進(jìn)行業(yè)務(wù)改造
開啟異步,設(shè)置整個異步接口處理的超時時間(2s),設(shè)置Listener主要用于處理接口超時,阻塞隊列處理查詢支付結(jié)果,查到結(jié)果后調(diào)用complete完成該長輪詢,如果2s沒有查到結(jié)果,那就返回查詢超時,客戶端繼續(xù)輪詢。
到此這篇關(guān)于Java Servlet線程中AsyncContext異步處理Http請求的文章就介紹到這了,更多相關(guān)Java AsyncContext異步處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Web使用簡單的批處理操作(記事本+Tomcat)
這篇文章主要介紹了Java Web使用簡單的批處理操作 ,需要的朋友可以參考下2014-10-10Java GUI圖形界面開發(fā)實現(xiàn)小型計算器流程詳解
本文章向大家介紹Java GUI圖形界面開發(fā)實現(xiàn)小型計算器,主要包括布局管理器使用實例、應(yīng)用技巧、基本知識點總結(jié)和需要注意事項,具有一定的參考價值,需要的朋友可以參考一下2022-08-08JDK8中String的intern()方法實例詳細(xì)解讀
String字符串在我們?nèi)粘i_發(fā)中最常用的,當(dāng)然還有他的兩個兄弟StringBuilder和StringBuilder,接下來通過本文給大家介紹JDK8中String的intern()方法詳細(xì)解讀,需要的朋友可以參考下2022-09-09Eclipse 出現(xiàn)A configuration with this name already exists問題解決方
這篇文章主要介紹了Eclipse 出現(xiàn)A configuration with this name already exists問題解決方法的相關(guān)資料,需要的朋友可以參考下2016-11-11Java基礎(chǔ)學(xué)習(xí)筆記之?dāng)?shù)組詳解
這篇文章主要介紹了Java基礎(chǔ)學(xué)習(xí)筆記之?dāng)?shù)組,結(jié)合實例形式詳細(xì)分析了java的基本概念、定義、迭代、輸出、反轉(zhuǎn)、排序等常用操作技巧,需要的朋友可以參考下2019-08-08Spring高級注解@PropertySource詳細(xì)解讀
這篇文章主要介紹了Spring高級注解@PropertySource詳細(xì)解讀,@PropertySource注解用于指定資源文件讀取的位置,它不僅能讀取properties文件,也能讀取xml文件,并且通過YAML解析器,配合自定義PropertySourceFactory實現(xiàn)解析yaml文件,需要的朋友可以參考下2023-11-11Java transient關(guān)鍵字與序列化操作實例詳解
這篇文章主要介紹了Java transient關(guān)鍵字與序列化操作,結(jié)合實例形式詳細(xì)分析了java序列化操作相關(guān)實現(xiàn)方法與操作注意事項,需要的朋友可以參考下2019-09-09