Detaillierte Erklärung, wie Tomcat asynchrone Servlets implementiert

Detaillierte Erklärung, wie Tomcat asynchrone Servlets implementiert

Vorwort

Durch meine vorherige Tomcat-Artikelserie glaube ich, dass die Studenten, die meinen Blog lesen, ein klareres Verständnis von Tomcat haben sollten. In den vorherigen Blogs haben wir besprochen, wie Tomcat im SpringBoot-Framework gestartet wird, wie die internen Komponenten von Tomcat entworfen sind und wie Anfragen fließen. Lassen Sie uns nun über Tomcats asynchrones Servlet sprechen, wie Tomcat asynchrones Servlet implementiert und die Verwendungsszenarien von asynchronem Servlet.

Praktisches asynchrones Servlet

Wir verwenden direkt das SpringBoot-Framework, um ein Servlet zu implementieren. Hier zeigen wir nur den Servlet-Code:

@WebServlet(urlPatterns = "/async",asyncSupported = true)
@Slf4j
öffentliche Klasse AsyncServlet erweitert HttpServlet {

 ExecutorService executorService = Executors.newSingleThreadExecutor();

 @Überschreiben
  geschützt void doGet(HttpServletRequest req, HttpServletResponse resp) wirft ServletException, IOException {
  //Asynchron starten und den asynchronen Kontext abrufen final AsyncContext ctx = req.startAsync();
  // Thread-Pool asynchrone Ausführung übermitteln executorService.execute(new Runnable() {


   @Überschreiben
   öffentliche Leere ausführen() {
    versuchen {
     log.info("Der asynchrone Dienst ist zur Ausführung bereit");
     //Zeitaufwändige Aufgaben simulieren Thread.sleep(10000L);
     ctx.getResponse().getWriter().print("asynchrones Servlet");
     log.info("asynchroner Dienst ausgeführt");
    } Fang (IOException e) {
     e.printStackTrace();
    } Fang (UnterbrocheneAusnahme e) {
     e.printStackTrace();
    }
    //Schließlich wird der Rückruf abgeschlossen, nachdem die Ausführung abgeschlossen ist.
    ctx.komplett();
   }
  });
 }

Der obige Code implementiert ein asynchrones Servlet und implementiert doGet . Beachten Sie, dass Sie in SpringBoot die Klasse starten und @ServletComponentScan hinzufügen müssen, um das Servlet zu scannen. Nachdem der Code nun geschrieben ist, sehen wir uns an, wie er tatsächlich funktioniert.

Nachdem wir eine Anfrage gesendet haben, sehen wir, dass die Seite antwortet und dass die Anfrage 10,05 Sekunden dauert, sodass unser Servlet normal läuft. Einige Studenten werden sicherlich fragen: Ist das nicht ein asynchrones Servlet? Was bringt es, wenn Ihre Reaktionszeit nicht schneller ist? Ja, unsere Antwortzeit kann nicht beschleunigt werden, es hängt immer noch von unserer Geschäftslogik ab, aber nach unserer asynchronen Servlet-Anforderung können wir, basierend auf der asynchronen Ausführung des Geschäfts, sofort zurückkehren, d. h. Tomcat-Threads können sofort recycelt werden. Standardmäßig gibt es 10 Kernthreads von Tomcat und die maximale Anzahl von Threads beträgt 200. Wir können Threads rechtzeitig recyceln, was bedeutet, dass wir mehr Anforderungen verarbeiten und unseren Durchsatz erhöhen können, was auch die Hauptfunktion des asynchronen Servlets ist.

Interna asynchroner Servlets

Nachdem wir die Rolle des asynchronen Servlets verstanden haben, schauen wir uns an, wie Tomcat das erste asynchrone Servlet ist. Tatsächlich besteht die Hauptkernlogik des obigen Codes aus zwei Teilen: final AsyncContext ctx = req.startAsync(); und ctx.complete(); Mal sehen, was sie tun.

 öffentliche AsyncContext startAsync(ServletRequest Anfrage,
   ServletResponse-Antwort) {
  wenn (!isAsyncSupported()) {
   IllegalStateException ise =
     neue IllegalStateException(sm.getString("request.asyncNotSupported"));
   log.warnen(sm.getString("coyoteRequest.noAsync",
     : StringUtils.join(getNonAsyncClassNames())), ise);
   wirf Ise;
  }

  wenn (asyncContext == null) {
   asyncContext = neuer AsyncContextImpl(dies);
  }

  asyncContext.setStarted(getContext(), Anfrage, Antwort,
    Anfrage==getRequest() und Antwort==getResponse().getResponse());
  asyncContext.setTimeout(getConnector().getAsyncTimeout());

  gibt asynchronen Kontext zurück;
 }

Wir haben festgestellt req.startAsync(); nur einen asynchronen Kontext speichert und einige grundlegende Informationen festlegt, wie z. B. Timeout . Das hier festgelegte Standard-Timeout beträgt übrigens 30 Sekunden, was bedeutet, dass Ihre asynchrone Verarbeitungslogik nach mehr als 30 Sekunden einen Fehler meldet. Zu diesem Zeitpunkt wird bei der Ausführung ctx.complete(); eine IllegalStateException ausgelöst.

Schauen wir uns die Logik von ctx.complete();

 öffentliche Leere komplett() {
  wenn (log.isDebugEnabled()) {
   logDebug("abgeschlossen");
  }
  überprüfen();
  request.getCoyoteRequest().action(ActionCode.ASYNC_COMPLETE, null);
 }
//Klasse: AbstractProcessor 
 öffentliche final void Aktion(Aktionscode Aktionscode, Objektparameter) {
 Fall ASYNC_COMPLETE: {
   : Löscht Dispatches();
   wenn (asyncStateMachine.asyncComplete()) {
    verarbeitenSocketEvent(SocketEvent.OPEN_READ, true);
   }
   brechen;
  } 
 }
 //Klasse: AbstractProcessor 
geschützt void processSocketEvent(SocketEvent-Ereignis, boolean dispatch) {
  SocketWrapperBase<?> socketWrapper = getSocketWrapper();
  wenn (socketWrapper != null) {
   socketWrapper.processSocket(Ereignis, Versand);
  }
 }
 //Klasse: AbstractEndpoint
öffentlicher boolescher ProzessSocket(SocketWrapperBase<S> socketWrapper,
   SocketEvent-Ereignis, Boolescher Dispatch) {
  //Einigen Code weglassen SocketProcessorBase<S> sc = null;
   if (ProzessorCache != null) {
    sc = ProzessorCache.pop();
   }
   wenn (sc == null) {
    sc = createSocketProcessor(socketWrapper, Ereignis);
   } anders {
    sc.reset(socketWrapper, Ereignis);
   }
   Executor Executor = getExecutor();
   wenn (Dispatch und Executor != null) {
    Executor.execute(sc);
   } anders {
    sc.run();
   }
 
  gibt true zurück;
 }

Daher wird hier die processSocket -Methode von AbstractEndpoint aufgerufen. Wer meinen vorherigen Blog gelesen hat, sollte den Eindruck haben, dass EndPoint zum Annehmen und Verarbeiten von Anforderungen verwendet wird und dann zur Protokollverarbeitung an Processor übergeben wird.

Klasse: AbstractProcessorLight
öffentlicher SocketState-Prozess (SocketWrapperBase<?> socketWrapper, SocketEvent-Status)
   wirft IOException {
  //Teil des Durchmessers weglassen
  SocketState-Status = SocketState.GESCHLOSSEN;
  Iterator<DispatchType> dispatches = null;
  Tun {
   wenn (dispatches != null) {
    DispatchType nextDispatch = dispatches.next();
    Status = Versand (nextDispatch.getSocketStatus());
   } sonst wenn (status == SocketEvent.DISCONNECT) {
   
   } sonst wenn (isAsync() || isUpgrade() || Status == SocketState.ASYNC_END) {
    Zustand = Versand(Status);
    wenn (Status == SocketState.OPEN) {
     Status = Dienst(SocketWrapper);
    }
   } sonst wenn (status == SocketEvent.OPEN_WRITE) {
    Zustand = SocketState.LONG;
   } sonst wenn (status == SocketEvent.OPEN_READ) {
    Status = Dienst(SocketWrapper);
   } anders {
    Zustand = SocketState.GESCHLOSSEN;
   }

  } während (Status == SocketState.ASYNC_END ||
    versendet != null und Status != SocketState.CLOSED);

  Rückgabezustand;
 }

Dieser Teil ist der entscheidende Punkt. AbstractProcessorLight bestimmt anhand des Status von SocketEvent , ob service(socketWrapper) aufgerufen werden soll. Diese Methode ruft schließlich den Container auf, um den Aufruf der Geschäftslogik abzuschließen. Unsere Anforderung wird aufgerufen, nachdem die Ausführung abgeschlossen ist, und darf nicht in den Container gelangen, da es sonst zu einer Endlosschleife kommt. Hier wird sie von isAsync() beurteilt und gelangt in dispatch(status) , und schließlich wird die Methode asyncDispatch von CoyoteAdapter aufgerufen.

öffentliches Boolean asyncDispatch(org.apache.coyote.Request req, org.apache.coyote.Response res,
   SocketEvent-Status) löst Exception aus {
  //Code weglassen Request request = (Request) req.getNote(ADAPTER_NOTES);
  Antwort Antwort = (Antwort) res.getNote(ADAPTER_NOTES);
  Boolescher Erfolg = wahr;
  AsyncContextImpl asyncConImpl = request.getAsyncContextInternal();
  versuchen {
   wenn (!request.isAsync()) {
    Antwort.setSuspended(false);
   }

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

   wenn (!request.isAsyncDispatching() && request.isAsync()) {
    WriteListener writeListener = res.getWriteListener();
    : LesenListener lesenListener = req.getReadListener();
    wenn (writeListener != null und status == SocketEvent.OPEN_WRITE) {
     ClassLoader alteCL = null;
     versuchen {
      alteCL = request.getContext().bind(false, null);
      res.onWritePossible(); //Hier die Browserantwort ausführen und Daten schreiben, wenn (request.isFinished() && req.sendAllDataReadEvent() &&
        readListener != null) {
       readListener.onAllDataRead();
      }
     } fangen (Wurfbares t) {
      
     Endlich
      request.getContext().unbind(false, oldCL);
     }
    } 
    }
   }
   // Hier wird beurteilt, dass Asynchronität im Gange ist, was bedeutet, dass dies kein Rückruf einer Vervollständigungsmethode ist, sondern eine normale asynchrone Anforderung, und der Container wird weiterhin aufgerufen.
   wenn (request.isAsyncDispatching()) {
    Connector.getService().getContainer().getPipeline().getFirst().aufrufen(
      Anfrage, Antwort);
    Throwable t = (Throwable) Anfrage.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
    wenn (t != null) {
     asyncConImpl.setErrorState(t, true);
    }
   }
   //Beachten Sie, dass hier im Falle eines Timeouts oder Fehlers request.isAsync() false zurückgibt, um den Fehler schnellstmöglich an den Client auszugeben.
   wenn (!request.isAsync()) {
    //Dies ist auch die Ausgabelogik request.finishRequest();
    Antwort.finishResponse();
   }
   //Anfrage und Antwort zerstören
   wenn (!Erfolg || !request.isAsync()) {
    updateWrapperErrorCount(Anfrage, Antwort);
    Anfrage.recycle();
    antwort.recycle();
   }
  }
  Erfolg zurückgeben;
 }

Der obige Code ist die letzte ctx.complete() ausgeführte Methode (natürlich werden viele Details ausgelassen), die die Datenausgabe abschließt und sie schließlich an den Browser ausgibt.

Einige Studenten sagen hier vielleicht: „Ich weiß, dass nach Abschluss der asynchronen Ausführung durch den Aufruf ctx.complete() eine Ausgabe an den Browser erfolgt, aber woher weiß Tomcat nach der Ausführung der ersten doGet-Anforderung, dass es nicht zum Client zurückkehren muss?“ Der Schlüsselcode befindet sich in der service im CoyoteAdapter . Ein Teil des Codes lautet wie folgt:

 postParseSuccess = postParseRequest(Anforderung, Anfrage, Antwort);
   //Code weglassen if (postParseSuccess) {
    Anfrage.setAsyncSupported(
      Connector.getService().getContainer().getPipeline().isAsyncSupported());
    Connector.getService().getContainer().getPipeline().getFirst().aufrufen(
      Anfrage, Antwort);
   }
   wenn (request.isAsync()) {
    asynchron = wahr;
    } anders {
    //Daten an den Client ausgeben request.finishRequest();
    Antwort.finishResponse();
   wenn (!async) {
    updateWrapperErrorCount(Anfrage, Antwort);
    //Anfrage und Antwort zerstören
    Anfrage.recycle();
    antwort.recycle();
   }

Nach dem Aufruf Servlet ermittelt dieser Teil des Codes mithilfe von request.isAsync() , ob es sich um eine asynchrone Anforderung handelt. Wenn es sich um eine asynchrone Anforderung handelt, setzen Sie async = true . Wenn es sich um eine nicht asynchrone Anforderung handelt, führen Sie die Logik zur Ausgabe von Daten an den Client aus und zerstören Sie request und response gleichzeitig. Damit wird der Vorgang abgeschlossen, dem Client nach Abschluss der Anforderung nicht zu antworten.

Warum die @EnableAsync-Annotation von Spring Boot kein asynchrones Servlet ist

Da ich bei der Vorbereitung dieses Artikels viele Informationen gesucht habe, habe ich festgestellt, dass viele Materialien zur asynchronen Programmierung mit SpringBoot auf der Annotation @EnableAsync basieren und dann Multithreading im Controller verwenden, um die Geschäftslogik zu vervollständigen und schließlich die Ergebnisse zusammenzufassen und die Rückgabeausgabe abzuschließen. Hier ist ein Beispiel für einen Artikel eines Goldgräbers: „SpringBoot Asynchronous Programming Guide for Newbies“. Dieser Artikel ist sehr leicht zu verstehen und sehr gut. Aus geschäftlicher Sicht handelt es sich tatsächlich um asynchrone Programmierung, aber es gibt ein Problem. Abgesehen von der parallelen Verarbeitung des Geschäfts ist es nicht für die gesamte Anforderung asynchron, d. h. der Tomcat-Thread kann nicht sofort freigegeben werden, wodurch der Effekt des asynchronen Servlets nicht erreicht werden kann. Hier habe ich auch eine Demo mit Bezug auf das oben genannte geschrieben. Lassen Sie uns überprüfen, warum sie nicht asynchron ist.

@RestController
@Slf4j
öffentliche Klasse TestController {
 @Autowired
 privater TestService-Dienst;

 @GetMapping("/hallo")
 öffentlicher Stringtest () {
  versuchen {
   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());
  } Fang (UnterbrocheneAusnahme e) {
   e.printStackTrace();
  } Fang (Ausführungsausnahme e) {
   e.printStackTrace();
  }
  gib "hallo" zurück;
 }
@Service
öffentliche Klasse TestService {
 @Async("asyncExecutor")
 public CompletableFuture<String> test1() wirft InterruptedException {
  Thread.sleep(3000L);
  returniere CompletableFuture.completedFuture("test1");
 }

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

 @Async("asyncExecutor")
 public CompletableFuture<String> test3() wirft InterruptedException {
  Thread.sleep(3000L);
  return CompletableFuture.completedFuture("test3");
 }
}
@SpringBootAnwendung
@EnableAsync
öffentliche Klasse TomcatdebugApplication {

 öffentliche statische void main(String[] args) {
  SpringApplication.run(TomcatdebugApplication.class, args);
 }

 @Bean(Name = "asyncExecutor")
 öffentlicher Executor asyncExecutor() {
  ThreadPoolTaskExecutor Executor = neuer ThreadPoolTaskExecutor();
  executor.setCorePoolSize(3);
  executor.setMaxPoolSize(3);
  executor.setQueueCapacity(100);
  executor.setThreadNamePrefix("AsynchThread-");
  Executor.initialisieren();
  Testamentsvollstrecker zurückgeben;
 }

Hier führe ich es aus und sehe die Wirkung

Hier habe ich nach meiner Anforderung einen Haltepunkt gesetzt, bevor ich den Container aufgerufen habe, um die Geschäftslogik auszuführen, und dann nach der Rückkehr einen Haltepunkt gesetzt. Nachdem Controller ausgeführt wurde, kehrte die Anforderung zu CoyoteAdapter zurück und beurteilte request.isAsync() . Laut der Abbildung ist es false , dann werden request.finishRequest() und response.finishResponse() ausgeführt, um das Ende der Antwort auszuführen und die Anforderungs- und Antworttexte zu zerstören. Interessanterweise habe ich beim Experimentieren festgestellt, dass der Antworttext bereits auf der Browserseite angezeigt wurde, bevor request.isAsync() ausgeführt wurde. Dies liegt daran, dass das SpringBoot-Framework ihn bereits über die Methode writeInternal in StringHttpMessageConverter ausgegeben hat.

Die Kernlogik der obigen Analyse besteht darin, dass der Tomcat-Thread, nachdem er CoyoteAdapter ausgeführt hat, um den Container aufzurufen, warten muss, bis die Anforderung zurückgegeben wird, dann feststellen muss, ob es sich um eine asynchrone Anforderung handelt, und dann die Anforderung verarbeiten muss. Nach Abschluss der Ausführung kann der Thread wiederverwendet werden. In meinem ersten asynchronen Servlet-Beispiel wird nach der Ausführung der doGet-Methode sofort zurückgekehrt, d. h. es wird direkt zur Logik von request.isAsync() übergegangen. Anschließend wird die Logik des gesamten Threads ausgeführt und der Thread wiederverwendet.

Lassen Sie uns über die Verwendungsszenarien des asynchronen Servlets sprechen

Was sind nach so vielen Analysen die Anwendungsszenarien für asynchrone Servlets? Tatsächlich können wir es analysieren, indem wir nur einen Punkt erfassen: Asynchrone Servlets verbessern den Durchsatz des Systems und können mehr Anfragen annehmen. Angenommen, Tomcat verfügt im Websystem nicht über genügend Threads und es warten viele Anfragen. Zu diesem Zeitpunkt kann die Optimierung auf Anwendungsebene des Websystems nicht mehr optimiert werden, d. h. die Antwortzeit der Geschäftslogik kann nicht verkürzt werden. Wenn Sie zu diesem Zeitpunkt die Wartezeit des Benutzers verkürzen und den Durchsatz erhöhen möchten, können Sie versuchen, ein asynchrones Servlet zu verwenden.

Nehmen wir ein praktisches Beispiel: Wenn wir beispielsweise ein SMS-System erstellen, hat das SMS-System sehr hohe Anforderungen an die Echtzeitleistung, sodass die Wartezeit so kurz wie möglich sein muss und die Sendefunktion tatsächlich dem Bediener zum Senden anvertraut wird, das heißt, wir müssen die Schnittstelle aufrufen. Angenommen, die Parallelität ist sehr hoch, dann ist es zu diesem Zeitpunkt, an dem das Geschäftssystem unsere SMS-Sendefunktion aufruft, möglich, dass unser Tomcat-Thread-Pool aufgebraucht ist und die verbleibenden Anforderungen in der Warteschlange warten. Zu diesem Zeitpunkt erhöht sich die Verzögerung der SMS. Um dieses Problem zu lösen, können wir ein asynchrones Servlet einführen, um mehr SMS-Sendeanforderungen anzunehmen und so die Verzögerung der SMS zu verringern.

Zusammenfassen

In diesem Artikel habe ich zunächst ein asynchrones Servlet von Hand geschrieben, die Rolle des asynchronen Servlets analysiert und erklärt, wie das asynchrone Servlet in Tomcat implementiert wird. Anschließend habe ich es auch anhand der im Internet beliebten asynchronen SpringBoot-Programmierung erklärt. In Tomcat gibt es kein asynchrones Servlet. Abschließend habe ich über die Verwendungsszenarien asynchroner Servlets gesprochen und die Situationen analysiert, in denen asynchrone Servlets ausprobiert werden können.

Das Obige ist der vollständige Inhalt dieses Artikels. Ich hoffe, er wird für jedermanns Studium hilfreich sein. Ich hoffe auch, dass jeder 123WORDPRESS.COM unterstützen wird.

Das könnte Sie auch interessieren:
  • Fallstricke bei der neueren Version des IDEA2021 Tomcat10-Servlets
  • Tiefgreifendes Verständnis der Erstellung und Implementierung von Servlets in Tomcat
  • Servlet und Tomcat_PowerNode Java Academy
  • So implementieren Sie ein asynchrones Servlet in Tomcat
  • Einführung und Verwendung des Servlet-Objektpools in Tomcat
  • Eine detaillierte Einführung in den Arbeitsmechanismus von Servlet in Tomcat
  • Detaillierte Erklärung, wie Tomcat die Servlet-Initialisierung aus der Quellcodeanalyse aufruft

<<:  So verwenden Sie den Geometrietyp von MySQL, um Längen- und Breitengrad-Distanzprobleme zu bewältigen

>>:  Vergleich zweier Implementierungsmethoden der Vue-Dropdown-Liste

Artikel empfehlen

Beispielcode eines CSS-responsiven Layoutsystems

Responsive Layoutsysteme sind in den heute gängig...

Vue implementiert einfache Rechnerfunktion

In diesem Artikelbeispiel wird der spezifische Co...

Beispielcode zur Implementierung eines Radardiagramms mit vue+antv

1. Abhängigkeit herunterladen npm installiere @an...

Das Erlebnis gestalten: Was auf dem Knopf liegt

<br />Vor Kurzem hat UCDChina eine Artikelse...

Vollständiger Code zur Implementierung der Vue-Backtop-Komponente

Wirkung: Code: <Vorlage> <div Klasse=&qu...

Installieren Sie Docker unter CentOS 7

Wenn Sie kein Linux-System haben, finden Sie unte...

CSS realisiert Div vollständig zentriert, ohne Höhe festzulegen

Erfordern Das Div unter dem Körper ist vertikal z...

Lösung für das Timeout-Problem bei der Installation von Docker-Compose mit PIP

1: Installationsbefehl pip install docker-compose...

Häufige Fehler beim Schreiben von HTML-Tags

Wir sollten besser aufpassen, denn die HTML-Poliz...

Kapitel zur Entwicklung von WeChat-Applets: Fallstricke

Vor kurzem habe ich an der Entwicklung des ersten...