どのようにTomcatはサーブレット非同期です

Tomcatの

序文

Tomcatのシリーズは、私の以前の記事で、私は、Tomcatある学生はTomcatが、我々が開始するための方法をSpringBootの枠組みの中で最初のいくつかのブログTomcatを議論し、明確な理解を持っており、内部のコンポーネントについて説明しなければならない私のブログを見て信じていますどのようにして転送を要求する方法、そして、私たちがここでの話を非同期Tomcatのサーブレットをブログ設計するために、Tomcatは、非同期および非同期サーブレットサーブレットの使用シナリオを実装する方法です。

ハンドラインおよび非同期サーブレット

ちょうどサーブレットコードを表示するには、SpringBootフレームワークと直接サーブレットたちを実装するには:

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

复制代码

上記のコードは、非同期サーブレットを実装して実現doGet注意がクラスプラスSpringBoot開始するために使用する必要がある@ServletComponentScan注釈がサーブレットをスキャンします。コードが書かれているので、のは、実際の営業成績を見てみましょう。

私たちは応答があるページを参照するための要求を送信した後、同時に、要求の時間を過ごした10.05sを参照してください、そして、我々は、通常の友人とみなさサーブレットを実行することができます。一部の学生は確かに、これは非同期サーブレットそれではない、聞いてきますか?あなたの応答時間とスピードアップしませんでしたが、それはどのような使用ですか?はい、私たちは、応答時間を短縮することができない、またはそれが私たちのビジネスロジックに依存しますが、非同期サーブレットのための私達の要求の後に、非同期実行がビジネスに依存し、我々はすぐに返すことができ、それは、Tomcatのスレッドがすぐに回復することができると言うことです、デフォルトでは、Tomcatのコアスレッドが10で、スレッドの最大数は200で、我々は速やかに我々はより多くの要求を処理できることを意味し、スレッドを回復することができ、我々は非同期サーブレットの主な役割であるスループットを向上させることができます。

非同期サーブレットの内部原則

あなたは非同期サーブレットの役割を理解すれば、我々は最初に非同期であるか、Tomcatのサーブレットを見ています。実際には、上記のコードは、メインのコアロジックは二つの部分であり、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;
    }
复制代码

だから、最終的に私はここに呼ぶの学生が印象持つべきである前に、私の以前のブログを読んで、メソッドのリクエストを受信して処理するのに使用されたが、次は引き渡されるプロトコル処理に。AbstractEndpointprocessSocketEndPointProcessor

类: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はそれをクライアントに返す方法を知っていないですか?キーコードCoyoteAdapterservice方法は、コードの一部は、次のように

  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ここでは操作が完了すると、クライアントの要求が終了に応答しません。

なぜ@EnableAsyncの春ブーツ非同期サーブレットをコメントできません

あなたは、彼が多くの情報を問い合わせるこの記事を書く準備が整うまでなので、書かSpringBoot非同期プログラミングが依存する多くの情報を見つける@EnableAsyncのノート、その後、Controllerビジネスロジック、最終的に要約した結果、完全なリターン出力を完了するために、複数のスレッドを使用します。ここでナゲッツは、物品、例えば、「に兄を取るSpringBoot非同期プログラミングガイド初心者が理解できる、ビジネスの観点から、非常に簡単に書かれた記事は非常に良好で、理解するために、」確かに非同期プログラミングが、そこにあります一つの問題は、直ちに非同期サーブレットの効果を達成することができないTomcatのスレッドを解放しない意味、要求全体ではなく、非同期のために、並列処理操作のために取っておきます。ここで私はまた、デモを書い上記を参照してください、我々はそれが非同期ではない理由で検証する必要があります。

@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要求戻るまで待ってから、要求が非同期であるかどうかを判断し、その要求を処理し、次いで再循環させることができるスレッドの実装後にしなければならない容器を呼び出した後。最初のI非同期サーブレットの例は、doGetメソッドの実装が完了した後、直接に、つまり、すぐに戻りますrequest.isAsync()終了し、その後、ロジックスレッド全体を実行するためのロジックを、スレッドが回収されます。

非同期サーブレットチャットの使用シナリオ

そんなに、その後、非同期サーブレットの使用シナリオを分析し、何がいますか?実際には、限り、我々はいくつかの分析を保持できるように、それはシステムのスループットは、より多くの要求を受け入れることができ、非同期サーブレットの増加です。TomcatのWebシステムのスレッドが十分でないと仮定すると、Webアプリケーションレベルのシステム最適化はもはや最適化することができ、時間を待っている要求の多くは、それはあなたがユーザーを減らしたい場合は、ビジネス・ロジックの応答時間、およびこの時間を短縮することではありません待機時間と非同期サーブレットの下の使用を試すことができ、スループットを向上させます。

実用例えば:など、高いリアルタイム要件のためのSMSシステムをメッセージングシステムを作るなど、できるだけ短い時間を待っている必要があり、我々は実際に我々がインターフェイスを呼び出す必要があること、デリゲートを送信するためにオペレータを送信しています、メッセージの遅延は、高い並列性を仮定し、その私たちの関数を呼び出すためにテキストメッセージを送信するために、ビジネス・システムは、私たちのTomcatのスレッドプールを置くことが可能である。この時間は、最大使用され、残りの要求はキュー、および、この時点で待機しますこの問題を解決するために、上がるだろう、我々はこのように、待ち時間SMSを減らし、テキストメッセージを送信するために多くの要求を受け入れ、非同期サーブレットを導入することができます。

概要

私は、手書きの非同期サーブレットからこの記事を開始した非同期サーブレットの役割を分析し、どのように内部のTomcatサーブレットが非同期であり、その後、私は、Tomcat内にない人気のインターネットSpringBoot非同期プログラミング命令に基づいてする必要があります非同期サーブレット。最後に、私はあなたが非同期サーブレットを試すことができますどのような状況下で分析非同期サーブレットの使用シナリオについて話しました。

おすすめ

転載: juejin.im/post/5d872a1b6fb9a06ad16fadb0