Spring基礎学習-springMVC非同期処理モード解析(DeferredResult/sseEmitterなど)

12433 ワード

本カタログ
  • 1. 背景
  • 2. SpringMVC非同期処理の概要
  • 2.1 Callable
  • 2.2 DeferredResult
  • 2.2.1 DeferredResult処理フロー
  • 2.2.2 DeferredResult使用例
  • 2.3 SseEmitter
  • 2.4 StreamingResponseBody

  • 3非同期処理ブロック
  • 参考資料
  • 1.背景
    Tomcatなどのアプリケーションサーバの接続スレッドプールは実際には制限されている.各接続リクエストは、スレッドプールの接続数を消費します.大量のデータに対するクエリー操作、外部システムが提供するサービスの呼び出し、IO密集型操作など、長い時間がかかる操作がある場合は、接続が長くかかり、この場合、この接続は解放されず、他の要求によって再利用されません.接続が多すぎると、サーバは各リクエストにタイムリーに応答できない可能性があります.極端な場合、スレッドプール内のすべての接続が消費されると、サーバは長時間サービスを提供できません.
    通常のシーンでは、クライアントはサーバの処理が完了してから戻るのを待つ必要があります.このシーンでは、クライアントがサーブレットを呼び出した後、その処理が戻るのを待つ必要があり、サーブレットが特定のコントローラを呼び出した後も戻るのを待つ必要があります.この場合、サーバ側の開発で最も一般的なシーンであり、サーバ側の処理時間が長くない場合に適している.SpringのControllerはデフォルトでこのようなサービスを提供しています.
    あるサービスの処理時間が長すぎる場合、メールの送信のように外部インタフェースを呼び出す必要があり、処理時間は呼び出し側の制御を受けないため、時間が長すぎると2つの深刻な結果が得られる.1つは、上述したような長時間の占有要求接続数であり、深刻な場合、サーバが応答を失う可能性がある.第二に、クライアントの待ち時間が長すぎると、フロントエンドアプリケーションのユーザーフレンドリー性が低下し、サーバの応答が長時間得られないため、クライアントが操作を繰り返す可能性が高く、サーバの負担が重くなり、アプリケーションがクラッシュする確率が大きくなる.このようなシーンに対応するために、バックグラウンドのスレッドプールが一般的に有効になり、要求を処理するControllerは、メールがスレッドプールに送信されるなどの時間のかかる操作をコミットし、すぐにフロントに戻ります.そのため、応答を処理する主なスレッドが短くなり、お客様が感じているのは、送信ボタンをクリックするとすぐにサーバからフィードバックされ、安心して他の仕事を処理し続けることです.実際にメールの送信が数秒遅れるということは、お客様にとってまったく感じられません.もちろん、アプリケーションは、スレッドプールにコミットされたタスクの実行が成功したか、実行に失敗した後、フロントエンドのどこかで失敗の具体的な状況を見ることができることを保証する必要があります.
    このシナリオはSpringでTaskExecutorまたはAsyncを使用して処理できます.使用方法については、Springベースラーニング-タスク実行(TaskExecutorおよびAsync)を参照してください.
    以上の2つのシナリオから,ある操作に時間がかかる場合,クライアントが戻ってくるのを待たなければならない場合,どのような方法で処理すべきかが容易に考えられる.Servlet3.このようなシーンを処理するために0に非同期要求処理が導入され、対応してSpringは3.2バージョンでサーブレットのこの特性を使用するために関連メカニズムが導入された.
    2.SpringMVC非同期処理の概要
    Springでは、次の作業のシーンを処理するために、時間のかかるタスクがアプリケーション・サーバの接続数を占め、クライアントが次の作業のシーンを処理するために時間のかかるタスクが戻るのを待つ必要があるメカニズムが導入されています.
  • CallableまたはDeferredResultをControllerの戻り値として用いる、非同期で単一の結果を返すシーン
  • を処理することができる.
  • ResponseBodyEmitter/sseEmitterまたはStreamingResponseBodyを使用して、複数の戻り値
  • をストリーミング処理する.
  • Controllerで応答クライアントを使用してサービスを呼び出し、応答データオブジェクト
  • を返す.
    2.1 Callable
    Callableは、ControllerでRequestMappingによって注記されたメソッドをそのまま使用して、オブジェクトを返します.使用例:
    @RequestMapping("/testCallable")
    public Callable testCallable() {
        logger.info("Controller    !");
        Callable callable = () -> {
            Thread.sleep(5000);
    
            logger.info("        !");
    
            return "succeed!";
        };
        logger.info("Controller    !");
        return callable;
    }

    ブラウザを使用したアクセスhttp://localhost/test/testCallableを選択すると、次のようになります.
    2018-03-12 22:38:05.547  INFO 4980 --- [p-nio-80-exec-2] c.l.t.b.e.controllers.TestController     : Controller    !
    2018-03-12 22:38:05.553  INFO 4980 --- [p-nio-80-exec-2] c.l.t.b.e.controllers.TestController     : Controller    !
    2018-03-12 22:38:10.560  INFO 4980 --- [      MvcAsync1] c.l.t.b.e.controllers.TestController     :         !

    次の結果が表示されます.
  • ブラウザは約5秒待ってから結果
  • を返す.
  • 印刷ログでは、Controllerは6 msで
  • を実行する.
  • 印刷ログでは、実際のタスク実行はMvcAsync 1というスレッドで実行され、Controllerが5 s実行した後に
  • が実行終了する.
    結論を得ることができます
    Callableオブジェクトに戻ると、実際のワークスレッドはバックグラウンドで処理され、Controllerはワークスレッドの処理が完了するのを待つ必要はありませんが、Springはワークスレッドの処理が完了した後にクライアントに戻ります.実行プロセスは次のとおりです.
  • クライアント要求サービス
  • SpringMVCはControllerを呼び出し、ControllerはCallbackオブジェクト
  • を返す.
  • SpringMVCはruquestを呼び出す.startAsyncは、TaskExecutorにCallbackをコミットして
  • を実行する.
  • DispatcherServiceletやFiltersなどはアプリケーションサーバスレッドから終了するが、Responseは依然としてオープン状態であり、つまりクライアント
  • に一時的に戻ることはない.
  • TaskExecutor呼び出しCallbackは結果を返し、SpringMVCはアプリケーションサーバに要求を送信して
  • 処理を継続する.
  • DispatcherServiceletは再び呼び出され、Callbackが返すオブジェクトの処理を継続し、最終的にクライアント
  • に返す.
    2.2 DeferredResult
    DeferredResultの使用方法はCallableと似ていますが、戻り結果が異なり、戻り時に実際の結果が生成されない可能性があり、実際の結果は別のスレッドでDeferredResultに設定される可能性があります.このクラスには、次の日常的な使用に関するプロパティが含まれています.
  • タイムアウト構成:構造関数によってタイムアウト時間をミリ秒単位で入力できます.設定結果を待ってから処理を続行してクライアントに戻る必要があるため、ずっと待っているとクライアントが応答しないため、この問題を回避するために相応のタイムアウトメカニズムが必要です.実際にこのタイムアウト時間を設定しなくても、アプリケーションサーバやSpringにはデフォルトのタイムアウトメカニズムがあります.
  • 結果設定:その結果はresultという名前の属性に格納されます.setResultメソッドを呼び出すことで属性を設定できます.このDeferredResultは生まれながらにしてマルチスレッド環境で使用されているため,このresult属性の読み書きにはロックがかかっている.

  • 次に、DeferredResultの処理フローについて説明し、比較的簡単な例を実現する.
    2.2.1 DeferredResult処理フロー
    DeferredResultの処理手順はCallbackと似ていますが、結果はDeferredResultではなく、他のスレッドによって同期によってオブジェクトに設定されている点が異なります.次のように実行されます.
  • クライアント要求サービス
  • SpringMVCはControllerを呼び出し、ControllerはDeferredResultオブジェクト
  • を返します.
  • SpringMVCはruquestを呼び出す.startAsync
  • DispatcherServiceletやFiltersなどはアプリケーションサーバスレッドから終了するが、Responseは依然としてオープン状態であり、つまりクライアント
  • に一時的に戻ることはない.
  • 他のスレッドは結果をDeferredResultに設定し、SpringMVCはアプリケーションサーバに要求を送信して
  • の処理を継続する.
  • DispatcherServiceletが再び呼び出され、DeferredResultでの結果の処理が続行され、最終的にクライアント
  • に戻る.
    2.2.2 DeferredResult使用例
    この例では、1つのControllerに2つのRequestMapping注記を追加する方法を示します.1つはDeferredResultのオブジェクトを返し、もう1つはこのオブジェクトの値を設定します.
    @RestController
    @RequestMapping("/test")
    public class TestController {
        private static final Logger logger = LoggerFactory.getLogger(TestController.class);
    
        @Autowired
        private AsyncService asyncService;
    
        private DeferredResult deferredResult = new DeferredResult<>();
    
         /**
         *   DeferredResult  
         *
         * @return
         */
        @RequestMapping("/testDeferredResult")
        public DeferredResult testDeferredResult() {
            return deferredResult;
        }
    
        /**
         *  DeferredResult       
         * @return
         */
        @RequestMapping("/setDeferredResult")
        public String setDeferredResult() {
            deferredResult.setResult("Test result!");
            return "succeed";
        }
    }

    最初のステップ:http://localhost/test/testDeferredResultクライアントは、一定時間が経過するまで待機し、2番目のステップをタイムアウトして新しいページアクセスを開始します.http://localhost/test/setDeferredResult最初のページが結果を返します.
    2.3 SseEmitter
    CallbackとDeferredResultは単一の結果を設定するために使用され、複数の結果がクライアントに返される必要がある場合は、SseEmitterやResponseBodyEmitterなどを使用することができます.次に、DeferredResultの例と同様に、例を直接見ます.
    @RestController
    @RequestMapping("/test")
    public class TestController {
        private static final Logger logger = LoggerFactory.getLogger(TestController.class);
    
        @Autowired
        private AsyncService asyncService;
    
        private DeferredResult deferredResult = new DeferredResult<>();
    
        private SseEmitter sseEmitter = new SseEmitter();
    
        /**
         *   SseEmitter    
         * 
         * @return
         */
        @RequestMapping("/testSseEmitter")
        public SseEmitter testSseEmitter() {
            return sseEmitter;
        }
    
        /**
         *  SseEmitter        
         * 
         * @return
         */
        @RequestMapping("/setSseEmitter")
        public String setSseEmitter() {
            try {
                sseEmitter.send(System.currentTimeMillis());
            } catch (IOException e) {
                logger.error("IOException!", e);
                return "error";  
            }
    
            return "Succeed!"; 
        }
    
         /**
         *  SseEmitter       
         *
         * @return
         */
        @RequestMapping("/completeSseEmitter")
        public String completeSseEmitter() {
            sseEmitter.complete();
    
            return "Succeed!";
        }
    }

    最初のアクセス:http://localhost/test/testSseEmitterステップ2の連続アクセス:http://localhost/test/setSseEmitterステップ3:http://localhost/test/completeSseEmitter結果は、第3のステップが実行されると、第1のステップのアクセスが終了することになります.
    2.4 StreamingResponseBody
    結果をResponseのOutputStreamに直接書き込むために使用します.ファイルのダウンロードなどの例:
    @GetMapping("/download")
    public StreamingResponseBody handle() {
        return new StreamingResponseBody() {
            @Override
            public void writeTo(OutputStream outputStream) throws IOException {
                // write...
            }
        };
    }

    3非同期処理ブロック
    非同期処理を行う場合、CallableProcessingInterceptorを使用してCallbackがパラメータを返す場合をブロックしてもよいし、DeferredResultProcessingInterceptorを使用してDeferredResultの場合をブロックしてもよい.AsyncHandlerInterceptorを直接使用することもできます.ブロッキングの使用は通常のブロッキングと変わらないため、ここでは展開されません.具体的には、Spring Bootブロッカーの例とソースコードの原理分析を参照してください.
    参考資料
  • https://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support