婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av

主頁 > 知識庫 > 詳解Tomcat是如何實現異步Servlet的

詳解Tomcat是如何實現異步Servlet的

熱門標簽:北海市地圖標注app 新科美甲店地圖標注 AI電銷機器人 源碼 外呼系統打哪顯哪 高德地圖標注論壇 新邵電銷機器人企業 湖北ai智能電銷機器人 江西外呼系統 蘭州ai電銷機器人招商

前言

通過我之前的Tomcat系列文章,相信看我博客的同學對Tomcat應該有一個比較清晰的了解了,在前幾篇博客我們討論了Tomcat在SpringBoot框架中是如何啟動的,討論了Tomcat的內部組件是如何設計以及請求是如何流轉的,那么我們這邊博客聊聊Tomcat的異步Servlet,Tomcat是如何實現異步Servlet的以及異步Servlet的使用場景。

手擼一個異步的Servlet

我們直接借助SpringBoot框架來實現一個Servlet,這里只展示Servlet代碼:

@WebServlet(urlPatterns = "/async",asyncSupported = true)
@Slf4j
public class AsyncServlet extends HttpServlet {

 ExecutorService executorService =Executors.newSingleThreadExecutor();

 @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  //開啟異步,獲取異步上下文
  final AsyncContext ctx = req.startAsync();
  // 提交線程池異步執行
  executorService.execute(new Runnable() {


   @Override
   public void run() {
    try {
     log.info("async Service 準備執行了");
     //模擬耗時任務
     Thread.sleep(10000L);
     ctx.getResponse().getWriter().print("async servlet");
     log.info("async Service 執行了");
    } catch (IOException e) {
     e.printStackTrace();
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    //最后執行完成后完成回調。
    ctx.complete();
   }
  });
 }

上面的代碼實現了一個異步的Servlet,實現了 doGet 方法注意在SpringBoot中使用需要再啟動類加上 @ServletComponentScan 注解來掃描Servlet。既然代碼寫好了,我們來看看實際運行效果。

我們發送一個請求后,看到頁面有響應,同時,看到請求時間花費了10.05s,那么我們這個Servlet算是能正常運行啦。有同學肯定會問,這不是異步servlet嗎?你的響應時間并沒有加快,有什么用呢?對,我們的響應時間并不能加快,還是會取決于我們的業務邏輯,但是我們的異步servlet請求后,依賴于業務的異步執行,我們可以立即返回,也就是說,Tomcat的線程可以立即回收,默認情況下,Tomcat的核心線程是10,最大線程數是200,我們能及時回收線程,也就意味著我們能處理更多的請求,能夠增加我們的吞吐量,這也是異步Servlet的主要作用。

異步Servlet的內部原理

了解完異步Servlet的作用后,我們來看看,Tomcat是如何是先異步Servlet的。其實上面的代碼,主要核心邏輯就兩部分, final AsyncContext ctx = req.startAsync();ctx.complete(); 那我們來看看他們究竟做了什么?

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

我們發現 req.startAsync(); 只是保存了一個異步上下文,同時設置一些基礎信息,比如 Timeout ,順便提一下,這里設置的默認超時時間是30S,也就是說你的異步處理邏輯超過30S后就會報錯,這個時候執行 ctx.complete(); 就會拋出IllegalStateException 異常。

我們來看看 ctx.complete(); 的邏輯

 public void complete() {
  if (log.isDebugEnabled()) {
   logDebug("complete ");
  }
  check();
  request.getCoyoteRequest().action(ActionCode.ASYNC_COMPLETE, null);
 }
//類:AbstractProcessor 
 public final void action(ActionCode actionCode, Object param) {
 case ASYNC_COMPLETE: {
   clearDispatches();
   if (asyncStateMachine.asyncComplete()) {
    processSocketEvent(SocketEvent.OPEN_READ, true);
   }
   break;
  } 
 }
 //類:AbstractProcessor 
protected void processSocketEvent(SocketEvent event, boolean dispatch) {
  SocketWrapperBase<?> socketWrapper = getSocketWrapper();
  if (socketWrapper != null) {
   socketWrapper.processSocket(event, dispatch);
  }
 }
 //類:AbstractEndpoint
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
   SocketEvent event, boolean dispatch) {
  //省略部分代碼
   SocketProcessorBase<S> sc = null;
   if (processorCache != null) {
    sc = processorCache.pop();
   }
   if (sc == null) {
    sc = createSocketProcessor(socketWrapper, event);
   } else {
    sc.reset(socketWrapper, event);
   }
   Executor executor = getExecutor();
   if (dispatch && executor != null) {
    executor.execute(sc);
   } else {
    sc.run();
   }
 
  return true;
 }

所以,這里最終會調用 AbstractEndpointprocessSocket 方法,之前看過我前面博客的同學應該有印象, EndPoint 是用來接受和處理請求的,接下來就會交給 Processor 去進行協議處理。

類:AbstractProcessorLight
public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)
   throws IOException {
  //省略部分diam
  SocketState state = SocketState.CLOSED;
  Iterator<DispatchType> dispatches = null;
  do {
   if (dispatches != null) {
    DispatchType nextDispatch = dispatches.next();
    state = dispatch(nextDispatch.getSocketStatus());
   } else if (status == SocketEvent.DISCONNECT) {
   
   } else if (isAsync() || isUpgrade() || state == SocketState.ASYNC_END) {
    state = dispatch(status);
    if (state == SocketState.OPEN) {
     state = service(socketWrapper);
    }
   } else if (status == SocketEvent.OPEN_WRITE) {
    state = SocketState.LONG;
   } else if (status == SocketEvent.OPEN_READ){
    state = service(socketWrapper);
   } else {
    state = SocketState.CLOSED;
   }

  } while (state == SocketState.ASYNC_END ||
    dispatches != null && state != SocketState.CLOSED);

  return state;
 }

這部分是重點, AbstractProcessorLight 會根據 SocketEvent 的狀態來判斷是不是要去調用 service(socketWrapper) ,該方法最終會去調用到容器,從而完成業務邏輯的調用,我們這個請求是執行完成后調用的,肯定不能進容器了,不然就是死循環了,這里通過 isAsync() 判斷,就會進入 dispatch(status) ,最終會調用 CoyoteAdapterasyncDispatch 方法

public boolean asyncDispatch(org.apache.coyote.Request req, org.apache.coyote.Response res,
   SocketEvent status) throws Exception {
  //省略部分代碼
  Request request = (Request) req.getNote(ADAPTER_NOTES);
  Response response = (Response) res.getNote(ADAPTER_NOTES);
  boolean success = true;
  AsyncContextImpl asyncConImpl = request.getAsyncContextInternal();
  try {
   if (!request.isAsync()) {
    response.setSuspended(false);
   }

   if (status==SocketEvent.TIMEOUT) {
    if (!asyncConImpl.timeout()) {
     asyncConImpl.setErrorState(null, false);
    }
   } else if (status==SocketEvent.ERROR) {
    
   }

   if (!request.isAsyncDispatching() && request.isAsync()) {
    WriteListener writeListener = res.getWriteListener();
    ReadListener readListener = req.getReadListener();
    if (writeListener != null && status == SocketEvent.OPEN_WRITE) {
     ClassLoader oldCL = null;
     try {
      oldCL = request.getContext().bind(false, null);
      res.onWritePossible();//這里執行瀏覽器響應,寫入數據
      if (request.isFinished() && req.sendAllDataReadEvent() &&
        readListener != null) {
       readListener.onAllDataRead();
      }
     } catch (Throwable t) {
      
     } finally {
      request.getContext().unbind(false, oldCL);
     }
    } 
    }
   }
   //這里判斷異步正在進行,說明這不是一個完成方法的回調,是一個正常異步請求,繼續調用容器。
   if (request.isAsyncDispatching()) {
    connector.getService().getContainer().getPipeline().getFirst().invoke(
      request, response);
    Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
    if (t != null) {
     asyncConImpl.setErrorState(t, true);
    }
   }
   //注意,這里,如果超時或者出錯,request.isAsync()會返回false,這里是為了盡快的輸出錯誤給客戶端。
   if (!request.isAsync()) {
    //這里也是輸出邏輯
    request.finishRequest();
    response.finishResponse();
   }
   //銷毀request和response
   if (!success || !request.isAsync()) {
    updateWrapperErrorCount(request, response);
    request.recycle();
    response.recycle();
   }
  }
  return success;
 }

上面的代碼就是 ctx.complete() 執行最終的方法了(當然省略了很多細節),完成了數據的輸出,最終輸出到瀏覽器。

這里有同學可能會說,我知道異步執行完后,調用 ctx.complete() 會輸出到瀏覽器,但是,第一次doGet請求執行完成后,Tomcat是怎么知道不用返回到客戶端的呢?關鍵代碼在 CoyoteAdapter 中的 service 方法,部分代碼如下:

 postParseSuccess = postParseRequest(req, request, res, response);
   //省略部分代碼
   if (postParseSuccess) {
    request.setAsyncSupported(
      connector.getService().getContainer().getPipeline().isAsyncSupported());
    connector.getService().getContainer().getPipeline().getFirst().invoke(
      request, response);
   }
   if (request.isAsync()) {
    async = true;
    } else {
    //輸出數據到客戶端
    request.finishRequest();
    response.finishResponse();
   if (!async) {
    updateWrapperErrorCount(request, response);
    //銷毀request和response
    request.recycle();
    response.recycle();
   }

這部分代碼在調用完 Servlet 后,會通過 request.isAsync() 來判斷是否是異步請求,如果是異步請求,就設置 async = true 。如果是非異步請求就執行輸出數據到客戶端邏輯,同時銷毀 requestresponse 。這里就完成了請求結束后不響應客戶端的操作。

為什么說Spring Boot的@EnableAsync注解不是異步Servlet

因為之前準備寫本篇文章的時候就查詢過很多資料,發現很多資料寫SpringBoot異步編程都是依賴于 @EnableAsync 注解,然后在 Controller 用多線程來完成業務邏輯,最后匯總結果,完成返回輸出。這里拿一個掘金大佬的文章來舉例《新手也能看懂的 SpringBoot 異步編程指南 》,這篇文章寫得很通俗易懂,非常不錯,從業務層面來說,確實是異步編程,但是有一個問題,拋開業務的并行處理來說,針對整個請求來說,并不是異步的,也就是說不能立即釋放Tomcat的線程,從而不能達到異步Servlet的效果。這里我參考上文也寫了一個demo,我們來驗證下,為什么它不是異步的。

@RestController
@Slf4j
public class TestController {
 @Autowired
 private TestService service;

 @GetMapping("/hello")
 public String test() {
  try {
   log.info("testAsynch Start");
   CompletableFuture<String> test1 = service.test1();
   CompletableFuture<String> test2 = service.test2();
   CompletableFuture<String> test3 = service.test3();
   CompletableFuture.allOf(test1, test2, test3);
   log.info("test1=====" + test1.get());
   log.info("test2=====" + test2.get());
   log.info("test3=====" + test3.get());
  } catch (InterruptedException e) {
   e.printStackTrace();
  } catch (ExecutionException e) {
   e.printStackTrace();
  }
  return "hello";
 }
@Service
public class TestService {
 @Async("asyncExecutor")
 public CompletableFuture<String> test1() throws InterruptedException {
  Thread.sleep(3000L);
  return CompletableFuture.completedFuture("test1");
 }

 @Async("asyncExecutor")
 public CompletableFuture<String> test2() throws InterruptedException {
  Thread.sleep(3000L);
  return CompletableFuture.completedFuture("test2");
 }

 @Async("asyncExecutor")
 public CompletableFuture<String> test3() throws InterruptedException {
  Thread.sleep(3000L);
  return CompletableFuture.completedFuture("test3");
 }
}
@SpringBootApplication
@EnableAsync
public class TomcatdebugApplication {

 public static void main(String[] args) {
  SpringApplication.run(TomcatdebugApplication.class, args);
 }

 @Bean(name = "asyncExecutor")
 public Executor asyncExecutor() {
  ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  executor.setCorePoolSize(3);
  executor.setMaxPoolSize(3);
  executor.setQueueCapacity(100);
  executor.setThreadNamePrefix("AsynchThread-");
  executor.initialize();
  return executor;
 }

這里我運行下,看看效果

這里我請求之后,在調用容器執行業務邏輯之前打了一個斷點,然后在返回之后的同樣打了一個斷點,在 Controller 執行完之后,請求才回到了 CoyoteAdapter 中,并且判斷 request.isAsync() ,根據圖中看到,是為 false ,那么接下來就會執行 request.finishRequest()response.finishResponse() 來執行響應的結束,并銷毀請求和響應體。很有趣的事情是,我實驗的時候發現,在執行 request.isAsync() 之前,瀏覽器的頁面上已經出現了響應體,這是SpringBoot框架已經通過 StringHttpMessageConverter 類中的 writeInternal 方法已經進行輸出了。

以上分析的核心邏輯就是,Tomcat的線程執行 CoyoteAdapter 調用容器后,必須要等到請求返回,然后再判斷是否是異步請求,再處理請求,然后執行完畢后,線程才能進行回收。而我一最開始的異步Servlet例子,執行完doGet方法后,就會立即返回,也就是會直接到 request.isAsync() 的邏輯,然后整個線程的邏輯執行完畢,線程被回收。

聊聊異步Servlet的使用場景

分析了這么多,那么異步Servlet的使用場景有哪些呢?其實我們只要抓住一點就可以分析了,就是異步Servlet提高了系統的吞吐量,可以接受更多的請求。假設web系統中Tomcat的線程不夠用了,大量請求在等待,而此時Web系統應用層面的優化已經不能再優化了,也就是無法縮短業務邏輯的響應時間了,這個時候,如果想讓減少用戶的等待時間,提高吞吐量,可以嘗試下使用異步Servlet。

舉一個實際的例子:比如做一個短信系統,短信系統對實時性要求很高,所以要求等待時間盡可能短,而發送功能我們實際上是委托運營商去發送的,也就是說我們要調用接口,假設并發量很高,那么這個時候業務系統調用我們的發送短信功能,就有可能把我們的Tomcat線程池用完,剩下的請求就會在隊列中等待,那這個時候,短信的延時就上去了,為了解決這個問題,我們可以引入異步Servlet,接受更多的短信發送請求,從而減少短信的延時。

總結

這篇文章我從手寫一個異步Servlet來開始,分析了異步Servlet的作用,以及Tomcat內部是如何實現異步Servlet的,然后我也根據互聯網上流行的SpringBoot異步編程來進行說明,其在Tomcat內部并不是一個異步的Servlet。最后,我談到了異步Servlet的使用場景,分析了什么情況下可以嘗試異步Servlet。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

標簽:海南 南陽 池州 大理 阿克蘇 黃石 自貢 黔東

巨人網絡通訊聲明:本文標題《詳解Tomcat是如何實現異步Servlet的》,本文關鍵詞  詳解,Tomcat,是,如何,實現,;如發現本文內容存在版權問題,煩請提供相關信息告之我們,我們將及時溝通與處理。本站內容系統采集于網絡,涉及言論、版權與本站無關。
  • 相關文章
  • 下面列出與本文章《詳解Tomcat是如何實現異步Servlet的》相關的同類信息!
  • 本頁收集關于詳解Tomcat是如何實現異步Servlet的的相關信息資訊供網民參考!
  • 推薦文章
    婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av
    欧美一a一片一级一片| 欧美三级三级三级| 不卡的av电影| 欧美激情在线一区二区三区| 国产一区日韩二区欧美三区| 欧美成人三级电影在线| 日韩高清在线不卡| 色噜噜狠狠成人中文综合| 中文字幕一区二区三区蜜月| 成人小视频免费在线观看| 久久嫩草精品久久久精品| 久久精品国产澳门| 久久久久9999亚洲精品| 成人免费视频免费观看| 亚洲欧洲另类国产综合| 在线亚洲人成电影网站色www| 一区二区三区欧美在线观看| 欧美综合一区二区| 日韩电影免费一区| 精品欧美久久久| av网站免费线看精品| 亚洲精品视频一区| 精品久久久久一区二区国产| 成人一级黄色片| 欧美精品123区| 亚洲国产精品一区二区久久| 亚洲精品一二三| 亚洲女与黑人做爰| 亚洲三级小视频| 亚洲免费观看在线观看| 亚洲一区二区三区在线播放| 亚洲国产精品人人做人人爽| 欧美日韩不卡在线| 日韩视频免费观看高清完整版 | 久久伊人蜜桃av一区二区| 欧美mv日韩mv| 久久久综合九色合综国产精品| 精品欧美一区二区在线观看| 精品国产免费一区二区三区四区 | 国产一区二区看久久| 韩国三级在线一区| 福利一区二区在线| 欧美色网站导航| 欧美xingq一区二区| 国产精品日韩精品欧美在线| 亚洲激情中文1区| 免费成人结看片| www.日韩精品| 欧美日韩免费高清一区色橹橹| 亚洲成年人网站在线观看| 欧美人成免费网站| 在线免费观看视频一区| 国产成人精品免费视频网站| 国模套图日韩精品一区二区| 日韩精品亚洲一区二区三区免费| 亚洲黄色尤物视频| 中文字幕日本不卡| 久久婷婷综合激情| 久久综合久久综合久久综合| 69久久99精品久久久久婷婷| 欧美日韩一级视频| 欧美精品xxxxbbbb| 91精品国产美女浴室洗澡无遮挡| 久久蜜桃香蕉精品一区二区三区| 久久se这里有精品| 日韩精品一区二区三区视频播放| 欧美久久久久久蜜桃| 亚洲国产cao| 日本在线不卡一区| 日本一区二区三级电影在线观看| 日韩午夜激情视频| 国产欧美1区2区3区| 色综合天天综合在线视频| fc2成人免费人成在线观看播放| 国产女人aaa级久久久级| 久久久久青草大香线综合精品| 亚洲一区二区三区在线| 午夜电影一区二区| 国产成人av电影在线| 精品精品国产高清一毛片一天堂| 色老汉一区二区三区| 美女视频第一区二区三区免费观看网站| 91麻豆swag| 欧美午夜一区二区三区免费大片| 一本大道av伊人久久综合| 日本韩国欧美在线| 91欧美一区二区| 欧美极品另类videosde| 美女视频一区在线观看| 色噜噜狠狠成人网p站| 日韩亚洲欧美一区| 中文在线免费一区三区高中清不卡| 欧美美女直播网站| 在线看不卡av| 成人免费看黄yyy456| 最近中文字幕一区二区三区| 久久麻豆一区二区| 欧美一区在线视频| 国产成人在线视频播放| 麻豆高清免费国产一区| 91精品国产色综合久久ai换脸 | 国产精品久久影院| 亚洲国产精品精华液2区45| 中文字幕一区av| 欧美一区中文字幕| 8x8x8国产精品| 欧美大胆人体bbbb| 国产成人综合网站| 激情偷乱视频一区二区三区| 亚洲网友自拍偷拍| 亚洲精品ww久久久久久p站 | 亚洲视频资源在线| 日韩欧美在线观看一区二区三区| 久久不见久久见免费视频1| 久久精品国产99久久6| 91麻豆国产香蕉久久精品| 国产福利不卡视频| 日韩欧美一卡二卡| 亚洲精品一二三区| 成人在线视频一区| 欧美疯狂性受xxxxx喷水图片| 成人一区二区视频| aaa亚洲精品| 91蝌蚪porny成人天涯| 91麻豆免费看| 欧美亚洲免费在线一区| av动漫一区二区| eeuss影院一区二区三区| 色综合天天综合给合国产| 99久久精品国产一区二区三区 | 日韩一区二区精品葵司在线| 亚洲影院免费观看| 欧美美女一区二区三区| 国产在线一区观看| 国产成人免费视频网站| 亚洲影视在线观看| 国产亚洲欧美日韩在线一区| 欧美经典三级视频一区二区三区| 老司机精品视频一区二区三区| 国产精品入口麻豆原神| av一区二区三区四区| 加勒比av一区二区| 亚洲成人av一区| 久久蜜臀中文字幕| 欧美吻胸吃奶大尺度电影| 在线观看免费一区| 久久99精品一区二区三区三区| 99久久久免费精品国产一区二区| 精品国精品自拍自在线| 欧美午夜精品一区| 国产大陆a不卡| 日韩成人一区二区| 亚洲精品午夜久久久| 久久久久久久久久久久久女国产乱| 日本韩国一区二区| 成人黄色av电影| 国产精品一卡二卡| 全国精品久久少妇| 一区二区三区鲁丝不卡| 国产精品久久久久久福利一牛影视 | 丝袜诱惑制服诱惑色一区在线观看| 久久久www免费人成精品| 欧美日本一区二区在线观看| 一本到不卡免费一区二区| 国产成人在线影院| 国产在线观看一区二区| 免费成人结看片| 天天综合色天天综合色h| 亚洲视频精选在线| 亚洲猫色日本管| 色综合天天综合网天天狠天天| 免费高清视频精品| 视频在线观看一区| 午夜电影一区二区三区| 亚洲国产成人精品视频| 亚洲免费观看高清| 亚洲自拍偷拍图区| 五月激情丁香一区二区三区| 美女视频黄频大全不卡视频在线播放| 欧美日韩不卡视频| 99国产欧美另类久久久精品 | 国产一区二区不卡在线 | 韩国精品免费视频| 亚洲成人1区2区| 中文字幕一区二区在线观看| 欧美一区二区三区四区视频 | 国产一区二区看久久| 国产一区二区按摩在线观看| 激情亚洲综合在线| 国产一区二区免费视频| 麻豆成人久久精品二区三区红| 乱中年女人伦av一区二区| 激情综合色丁香一区二区| 麻豆成人av在线| 奇米888四色在线精品| 麻豆极品一区二区三区| 国产麻豆精品久久一二三| 国产+成+人+亚洲欧洲自线| a美女胸又www黄视频久久| 成人h动漫精品一区二区|