序文 これまでの Tomcat シリーズの記事を通じて、私のブログを読んでいる学生は Tomcat についてより明確に理解できるはずです。以前のブログでは、SpringBoot フレームワークで Tomcat がどのように起動されるか、Tomcat の内部コンポーネントがどのように設計されているか、リクエストがどのように流れるかについて説明しました。次に、Tomcat の非同期サーブレット、Tomcat が非同期サーブレットを実装する方法、および非同期サーブレットの使用シナリオについて説明します。 非同期サーブレットの実践 サーブレットを実装するために、SpringBoot フレームワークを直接使用します。ここでは、サーブレット コードのみを示します。 @WebServlet(urlPatterns = "/async",asyncSupported = true) 翻訳者 パブリッククラスAsyncServletはHttpServletを拡張します{ ExecutorService executorService =Executors.newSingleThreadExecutor(); @オーバーライド 保護された void doGet(HttpServletRequest req, HttpServletResponse resp) は ServletException、IOException をスローします { //非同期を開始し、非同期コンテキストを取得します。final AsyncContext ctx = req.startAsync(); // スレッドプールの非同期実行を送信します。executorService.execute(new Runnable() { @オーバーライド パブリックボイド実行() { 試す { log.info("非同期サービスの実行準備ができました"); //時間のかかるタスクをシミュレートする Thread.sleep(10000L); ctx.getResponse().getWriter().print("非同期サーブレット"); log.info("非同期サービスが実行されました"); } キャッチ (IOException e) { e.printStackTrace(); } キャッチ (InterruptedException e) { e.printStackTrace(); } //最後に、実行が完了するとコールバックが完了します。 ctx.complete(); } }); } 上記のコードは非同期サーブレットを実装し、 リクエストを送信すると、ページが応答し、リクエストに 10.05 秒かかることがわかります。つまり、サーブレットは正常に実行されています。学生の中には、これは非同期サーブレットではないのかと尋ねる人もいるでしょう。応答時間が速くなければ意味がありません。はい、応答時間は短縮できません。依然としてビジネス ロジックに依存しますが、非同期サーブレット要求の後、ビジネスの非同期実行に依存して、すぐに戻ることができます。つまり、Tomcat のスレッドをすぐにリサイクルできます。デフォルトでは、Tomcat のコア スレッドは 10 で、最大スレッド数は 200 です。時間内にスレッドをリサイクルできるため、より多くの要求を処理してスループットを向上させることができます。これは、非同期サーブレットの主な機能でもあります。 非同期サーブレットの内部 非同期サーブレットの役割を理解した後、Tomcat がどのようにして最初の非同期サーブレットになったのかを見てみましょう。実際、上記のコードの主なコアロジックは、 パブリックAsyncContext startAsync(ServletRequest リクエスト、 ServletResponse レスポンス) { (!isAsyncSupported())の場合{ 不正な状態例外 ise = 新しい IllegalStateException(sm.getString("request.asyncNotSupported")); log.warn(sm.getString("coyoteRequest.noAsync", StringUtils.join(getNonAsyncClassNames()))、ise); 投げる } (asyncContext == null)の場合{ asyncContext = 新しい AsyncContextImpl(this); } asyncContext.setStarted(getContext(), リクエスト, レスポンス, リクエスト==getRequest() && レスポンス==getResponse().getResponse()); asyncContext.setTimeout(getConnector().getAsyncTimeout()); asyncContext を返します。 } パブリックボイドコンプリート(){ ログがデバッグ有効の場合 logDebug("完了 "); } チェック(); リクエスト.getCoyoteRequest().action(ActionCode.ASYNC_COMPLETE, null); } //クラス: AbstractProcessor パブリックファイナルボイドアクション(アクションコードアクションコード、オブジェクトパラメータ) { ASYNC_COMPLETEの場合: { ディスパッチをクリアします。 (asyncStateMachine.asyncComplete())の場合{ SocketEvent を処理します (SocketEvent.OPEN_READ、true)。 } 壊す; } } //クラス: AbstractProcessor 保護されたvoid processSocketEvent(SocketEventイベント、ブールディスパッチ) { SocketWrapperBase<?> socketWrapper = getSocketWrapper(); ソケットラッパーが null の場合 socketWrapper.processSocket(イベント、ディスパッチ); } } //クラス: AbstractEndpoint パブリックブール型プロセスソケット(SocketWrapperBase<S> socketWrapper, SocketEvent イベント、ブールディスパッチ){ //一部のコードを省略 SocketProcessorBase<S> sc = null; プロセッサキャッシュが null の場合 sc = プロセッサキャッシュ.pop(); } (sc == null)の場合{ sc = createSocketProcessor(socketWrapper、イベント); } それ以外 { sc.reset(socketWrapper、イベント); } 実行者 executor = getExecutor(); if (ディスパッチ && エグゼキュータ != null) { 実行者.execute(sc); } それ以外 { sc.run(); } true を返します。 } そのため、ここでは クラス: AbstractProcessorLight パブリック SocketState プロセス (SocketWrapperBase<?> socketWrapper、SocketEvent ステータス) IOException をスローします { //直径の一部を省略 SocketState 状態 = SocketState.CLOSED; イテレータ<DispatchType> ディスパッチ = null; する { if (ディスパッチ!= null) { ディスパッチタイプ nextDispatch = dispatches.next(); 状態 = ディスパッチ(nextDispatch.getSocketStatus()); } そうでない場合 (ステータス == SocketEvent.DISCONNECT) { } そうでない場合 (isAsync() || isUpgrade() || state == SocketState.ASYNC_END) { 状態 = ディスパッチ(ステータス); 状態 == SocketState.OPEN の場合 { 状態 = サービス(socketWrapper); } } そうでない場合 (ステータス == SocketEvent.OPEN_WRITE) { 状態 = SocketState.LONG; } それ以外の場合 (ステータス == SocketEvent.OPEN_READ) { 状態 = サービス(socketWrapper); } それ以外 { 状態 = SocketState.CLOSED; } } while (state == SocketState.ASYNC_END || ディスパッチ != null && 状態 != SocketState.CLOSED); 状態を返します。 } この部分がポイントです。AbstractProcessorLight パブリックブールasyncDispatch(org.apache.coyote.Request req, org.apache.coyote.Response res, SocketEventステータス)例外をスローします{ //一部のコードを省略 Request request = (Request) req.getNote(ADAPTER_NOTES); レスポンス response = (レスポンス) res.getNote(ADAPTER_NOTES); ブール値の成功 = true; AsyncContextImpl は、リクエストの getAsyncContextInternal() を返します。 試す { リクエストが非同期の場合 レスポンスを一時停止に設定します(false); } (ステータス==SocketEvent.TIMEOUT)の場合{ (!asyncConImpl.timeout())の場合{ asyncConImpl.setErrorState(null、false); } } そうでない場合 (ステータス == SocketEvent.ERROR) { } リクエストが非同期ディスパッチされている場合、 書き込みリスナー writeListener = res.getWriteListener(); ReadListener の readListener = req.getReadListener(); if (writeListener != null && status == SocketEvent.OPEN_WRITE) { クラスローダー oldCL = null; 試す { oldCL = request.getContext().bind(false, null); res.onWritePossible(); //ここでブラウザのレスポンスを実行し、データを書き込みます if (request.isFinished() && req.sendAllDataReadEvent() && readListener != null) { readListener.onAllDataRead(); } } キャッチ (Throwable t) { ついに リクエスト.getContext().unbind(false, oldCL); } } } } //ここでは非同期が進行中であると判断されます。つまり、これは完了メソッドのコールバックではなく、通常の非同期リクエストであり、コンテナは引き続き呼び出されます。 リクエストが非同期にディスパッチされるかどうか コネクタ.getService().getContainer().getPipeline().getFirst().invoke() リクエスト、レスポンス); Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); t != nullの場合{ asyncConImpl.setErrorState(t, true); } } //ここで、タイムアウトまたはエラーが発生した場合、request.isAsync() は false を返し、できるだけ早くクライアントにエラーを出力することに注意してください。 リクエストが非同期の場合 //これも出力ロジック request.finishRequest(); レスポンスを終了します。 } //リクエストとレスポンスを破棄する if (!成功 || !request.isAsync()) { updateWrapperErrorCount(リクエスト、レスポンス); リクエスト.recycle(); レスポンスをリサイクルします。 } } 成功を返します。 } 上記のコードは、 ここで、非同期実行が完了した後、 postParseSuccess = postParseRequest(req、リクエスト、res、応答); //一部のコードを省略 if (postParseSuccess) { リクエスト.setAsyncSupported() コネクタ.getService().getContainer().getPipeline().isAsyncSupported()); コネクタ.getService().getContainer().getPipeline().getFirst().invoke() リクエスト、レスポンス); } リクエストが非同期の場合 非同期 = true; } それ以外 { // クライアントにデータを出力 request.finishRequest(); レスポンスを終了します。 非同期の場合 updateWrapperErrorCount(リクエスト、レスポンス); //リクエストとレスポンスを破棄する リクエスト.recycle(); レスポンスをリサイクルします。 } Spring Boot の @EnableAsync アノテーションが非同期サーブレットではない理由 この記事を書く準備をしていたときに多くの情報を検索したところ、SpringBoot 非同期プログラミングに関する多くの資料が @レストコントローラ 翻訳者 パブリッククラスTestController{ オートワイヤード プライベート TestService サービス。 @GetMapping("/hello") パブリック文字列テスト(){ 試す { log.info("testAsynch 開始"); 完了可能なFuture<String> test1 = service.test1(); 完了可能なFuture<String> test2 = service.test2(); 完了可能なFuture<String> test3 = service.test3(); テスト1、テスト2、テスト3の完了可能なFuture。 ログ情報("test1=====" + test1.get()); ログ情報("test2=====" + test2.get()); ログ情報("test3=====" + test3.get()); } キャッチ (InterruptedException e) { e.printStackTrace(); } キャッチ (ExecutionException e) { e.printStackTrace(); } 「hello」を返します。 } @サービス パブリッククラスTestService{ @Async("asyncExecutor") パブリックCompletableFuture<String> test1()はInterruptedExceptionをスローします{ スレッドスリープ(3000L); CompletableFuture.completedFuture("test1") を返します。 } @Async("asyncExecutor") パブリックCompletableFuture<String> test2()はInterruptedExceptionをスローします{ スレッドスリープ(3000L); CompletableFuture.completedFuture("test2") を返します。 } @Async("asyncExecutor") パブリックCompletableFuture<String> test3()はInterruptedExceptionをスローします{ スレッドスリープ(3000L); CompletableFuture.completedFuture("test3") を返します。 } } @SpringBootアプリケーション @非同期を有効にする パブリッククラスTomcatdebugApplication { パブリック静的voidメイン(String[] args) { TomcatdebugApplication.class を SpringApplication.run します。 } @Bean(名前 = "asyncExecutor") パブリックエグゼキュータ asyncExecutor() { ThreadPoolTaskExecutor 実行プログラム = 新しい ThreadPoolTaskExecutor(); executor.setCorePoolSize(3); 実行者.setMaxPoolSize(3); 実行者.setQueueCapacity(100); executor.setThreadNamePrefix("AsynchThread-"); 実行者を初期化します。 実行者を返す。 } ここで実行して効果を確認します ここでは、リクエスト後、コンテナを呼び出してビジネスロジックを実行する前にブレークポイントを設定し、戻った後にブレークポイントを設定しています。 上記の分析の核となるロジックは、Tomcat のスレッドが 非同期サーブレットの使用シナリオについてお話ししましょう ここまで分析した結果、非同期サーブレットの使用シナリオはどのようなものになるでしょうか?実際には、非同期サーブレットによってシステムのスループットが向上し、より多くのリクエストを受け入れることができるという 1 つのポイントを把握するだけで分析できます。 Web システム内の Tomcat のスレッド数が足りず、大量のリクエストが待機しているとします。このとき、Web システムのアプリケーション レベルでの最適化、つまりビジネス ロジックの応答時間を短縮できなくなります。このとき、ユーザーの待機時間を短縮してスループットを上げたい場合は、非同期サーブレットの使用を試してみるとよいでしょう。 実際の例を見てみましょう。たとえば、テキスト メッセージ システムを作成する場合、テキスト メッセージ システムはリアルタイム パフォーマンスに対する要件が非常に高いため、待機時間をできるだけ短くする必要があり、送信機能は実際にはオペレーターに送信を委託します。つまり、インターフェイスを呼び出す必要があります。同時実行性が非常に高いと仮定すると、この時点でビジネス システムがテキスト メッセージ送信機能を呼び出し、Tomcat スレッド プールが使い果たされ、残りの要求がキューで待機する可能性があります。このとき、テキスト メッセージの遅延が増加します。この問題を解決するには、非同期サーブレットを導入して、より多くのテキスト メッセージ送信要求を受け入れ、テキスト メッセージの遅延を減らすことができます。 要約する この記事では、まず手動で非同期サーブレットを作成し、非同期サーブレットの役割と、Tomcat 内で非同期サーブレットがどのように実装されているかを分析しました。次に、インターネットで人気の SpringBoot 非同期プログラミングに基づいて説明しました。これは Tomcat 内の非同期サーブレットではありません。最後に、非同期サーブレットの使用シナリオについて説明し、非同期サーブレットを試すことができる状況を分析しました。 以上がこの記事の全内容です。皆様の勉強のお役に立てれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。 以下もご興味があるかもしれません:
|
<<: MySQL のジオメトリ型を使用して経度と緯度の距離の問題を処理する方法
ステップ1: MySQL YUMソースを取得するMySQLの公式サイトにアクセスして、RPMパッケー...
この記事では、参考までに、シンプルなナビゲーションバーを実装するためのJavascriptの具体的な...
1. xshell を使用して仮想マシンに接続するか、仮想マシンに直接コマンドを入力します。以下はx...
起動時に Ubuntu デュアル システムが停止する問題の解決方法 (Ubuntu 16.04 およ...
目次解決された主な問題1. バックエンドから返され、バックエンドに送信されるデータは、次の形式になり...
この記事の例では、ウォーターフォールフローレイアウトを実装するためのjsの具体的なコードを参考までに...
幅: 自動子要素(コンテンツ+パディング+境界線+余白を含む)は、親要素のコンテンツ領域全体を埋めま...
Linux に MySQL をインストールするには、yum インストールとソース コード コンパイ...
基本構造:コードをコピーコードは次のとおりです。 <ダウンロード> <dt>...
この記事では、画像フォロー効果を実現するためのjQueryの具体的なコードを参考までに紹介します。具...
この記事では、Vueでアップロードされた画像に透かしを追加する具体的な実装コードを参考までに共有しま...
この記事で使用されているPHPベースイメージはphp:7.3-apacheです。この記事の Lara...
主要な Web サイトと個人的な習慣に従って、Docker ソースを追加するには次の方法を使用します...
目次1. 静的実装方法: 2. 第2のシミュレーション動的方法3. 3番目の動的データ方式4. 動的...
目次ショートポーリングロングポーリングウェブソケットコミュニケーションの原則シンプルな1対1チャット...