web-dev-qa-db-ja.com

実行時間を増やすことなく、DeferredResultsを返す非同期サービスを複数回呼び出します

私のアプリケーションには2つのコアエンドポイントが必要です:プッシュプル送信とデータのフェッチ。

プル操作は非同期で機能し、結果はDeferredResultになります。ユーザーがプルサービスを休止状態で呼び出すと、新しいDefferedResultが作成され、Map<Long, DefferedResult> results = new ConcurrentHashMap<>()に格納されます。ここで、新しいデータを待機するか、タイムアウトが期限切れになるまで待機します。

プッシュ操作は、ユーザーを休憩中に呼び出します。この操作は、この操作によってプッシュされたデータの受信者の結果のマップをチェックします。マップに受信者の結果が含まれている場合、これらのデータは受信者の結果に設定され、DefferedResultが返されます。

基本コードは次のとおりです。

@Service
public class FooServiceImpl {
    Map<Long, DefferedResult> results = new ConcurrentHashMap<>();

    @Transactional
    @Override
    public DeferredResult<String> pull(Long userId) {
        // here is database call, String data = fooRepository.getNewData(); where I check if there are some new data in database, and if there are, just return it, if not add deferred result into collection to wait for it
        DeferredResult<String> newResult = new DeferredResult<>(5000L);
        results.putIfAbsent(userId, newResult);
        newResult.onCompletion(() -> results.remove(userId));

        // if (data != null)
        //      newResult.setResult(data);

        return newResult;
    }

    @Transactional
    @Override
    public void Push(String data, Long recipientId) {
        // fooRepository.save(data, recipientId);
        if (results.containsKey(recipientId)) {
            results.get(recipientId).setResult(data);
        }
    }
}

コードは期待どおりに機能しています。問題は、複数のユーザーに対しても機能するはずです。プル操作を呼び出す最大アクティブユーザーは最大1000になると思います。したがって、DefferedResultで設定したように、プルのすべての呼び出しには最大5秒かかりますが、そうではありません。

画像でわかるように、JavaScriptクライアントから残りのプル操作をすぐに複数回呼び出すと、タスクが同時にではなく順番に実行されることがわかります。最後に実行したタスクには約25秒かかりますが、1000人のユーザーが同時にプル操作を実行する場合、その操作には最大5秒+レイテンシーが必要です。

enter image description here

これらのタスクを同時に実行し、各タスクが約5秒以下になるようにアプリを構成するにはどうすればよいですか(別のユーザーが待機中のユーザーに何かを送信した場合)?この構成をプロパティファイルに追加してみました:

server.Tomcat.max-threads=1000

また、この構成:

@Configuration
public class AsyncConfig extends AsyncSupportConfigurer {

    @Override
    protected AsyncTaskExecutor getTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(1000);
        taskExecutor.initialize();
        return taskExecutor;
    }
}

しかし、それは役に立たなかった、それでも同じ結果。設定を手伝ってもらえますか?

編集:

これは私がAngularからこのサービスを呼び出す方法です:

this.http.get<any>(this.url, {params})
  .subscribe((data) => {
    console.log('s', data);
  }, (error) => {
    console.log('e', error);
  });

このような純粋なJSコードで何度も呼び出してみたところ:

function httpGet()
{
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open( "GET", 'http://localhost:8080/api/pull?id=1', true );
    xmlHttp.send( null );
    return xmlHttp.responseText;
}
setInterval(httpGet, 500);

すべてのリクエスト呼び出しをはるかに高速に実行します(約7秒)。増加するとデータベースがインサービスで呼び出されると予想しましたが、それでも25秒よりはましです。このサービスをAngularで呼び出すことに何か問題がありますか?

編集2:

別の形式のテストを試し、ブラウザーの代わりにjMeterを使用しました。 100スレッドで100リクエストを実行すると、結果は次のようになります。

enter image description here

ご覧のとおり、リクエストは10まで進行し、50リクエストに達すると、アプリケーションは例外をスローします。

Java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.
    at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.Java:667) ~[HikariCP-2.7.8.jar:na]
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.Java:183) ~[HikariCP-2.7.8.jar:na]
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.Java:148) ~[HikariCP-2.7.8.jar:na]
    at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.Java:128) ~[HikariCP-2.7.8.jar:na]
    at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.Java:122) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final]
    at org.hibernate.internal.NonContextualJdbcConnectionAccess.obtainConnection(NonContextualJdbcConnectionAccess.Java:35) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final]
    at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.acquireConnectionIfNeeded(LogicalConnectionManagedImpl.Java:106) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final]
    at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.getPhysicalConnection(LogicalConnectionManagedImpl.Java:136) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final]
    at org.hibernate.internal.SessionImpl.connection(SessionImpl.Java:523) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final]
    at Sun.reflect.GeneratedMethodAccessor61.invoke(Unknown Source) ~[na:na]
    at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43) ~[na:1.8.0_171]
    at Java.lang.reflect.Method.invoke(Method.Java:498) ~[na:1.8.0_171]
    at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.Java:223) ~[spring-core-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.Java:207) ~[spring-core-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle.doGetConnection(HibernateJpaDialect.Java:391) ~[spring-orm-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.beginTransaction(HibernateJpaDialect.Java:154) ~[spring-orm-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.Java:400) ~[spring-orm-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.Java:378) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.Java:474) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.Java:289) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.Java:98) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.Java:185) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.Java:92) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.Java:185) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.Java:689) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at sk.moe.zoya.service.impl.FooServiceImpl$$EnhancerBySpringCGLIB$$ebab570a.pull(<generated>) ~[classes/:na]
    at sk.moe.zoya.web.FooController.pull(FooController.Java:25) ~[classes/:na]
    at Sun.reflect.GeneratedMethodAccessor60.invoke(Unknown Source) ~[na:na]
    at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43) ~[na:1.8.0_171]
    at Java.lang.reflect.Method.invoke(Method.Java:498) ~[na:1.8.0_171]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.Java:209) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.Java:136) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.Java:102) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.Java:877) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.Java:783) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.Java:87) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.Java:991) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.Java:925) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.Java:974) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.Java:866) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.Java:635) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.Java:851) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.Java:742) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:231) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:166) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.Tomcat.websocket.server.WsFilter.doFilter(WsFilter.Java:52) ~[Tomcat-embed-websocket-8.5.29.jar:8.5.29]
    at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:193) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:166) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.Java:99) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.Java:107) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:193) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:166) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.Java:109) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.Java:107) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:193) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:166) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.Java:81) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.Java:107) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:193) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:166) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.Java:200) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.Java:107) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:193) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:166) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.Java:198) ~[Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.core.StandardContextValve.invoke(StandardContextValve.Java:96) [Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.Java:496) [Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.core.StandardHostValve.invoke(StandardHostValve.Java:140) [Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.Java:81) [Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.Java:87) [Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.Java:342) [Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.coyote.http11.Http11Processor.service(Http11Processor.Java:803) [Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.Java:66) [Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.Java:790) [Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.Tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.Java:1459) [Tomcat-embed-core-8.5.29.jar:8.5.29]
    at org.Apache.Tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.Java:49) [Tomcat-embed-core-8.5.29.jar:8.5.29]
    at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1149) [na:1.8.0_171]
    at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:624) [na:1.8.0_171]
    at org.Apache.Tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.Java:61) [Tomcat-embed-core-8.5.29.jar:8.5.29]
    at Java.lang.Thread.run(Thread.Java:748) [na:1.8.0_171]

2018-06-02 13:21:47.163  WARN 26978 --- [io-8080-exec-48] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: null
2018-06-02 13:21:47.163  WARN 26978 --- [io-8080-exec-40] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: null
2018-06-02 13:21:47.163 ERROR 26978 --- [io-8080-exec-48] o.h.engine.jdbc.spi.SqlExceptionHelper   : HikariPool-1 - Connection is not available, request timed out after 30000ms.
2018-06-02 13:21:47.163 ERROR 26978 --- [io-8080-exec-40] o.h.engine.jdbc.spi.SqlExceptionHelper   : HikariPool-1 - Connection is not available, request timed out after 30000ms.
2018-06-02 13:21:47.164 ERROR 26978 --- [io-8080-exec-69] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection] with root cause

また、リポジトリを使用してデータベースに何もないことを確認するコードにコメントし、同じ結果を出します。また、AtomicLongクラスを使用してリクエストごとに一意のuserIdを設定しました。

編集3:

コメントするとわかります@Transactionalすべてが正常に機能します!では、遅延を増やすことなく、大量の操作に対してSpringのトランザクションを設定する方法を教えてください。

追加した spring.datasource.maximumPoolSize=1000プールのサイズを増やす必要があると思うので、唯一の問題は@Transactionalを使用してメソッドを高速化する方法です。

最初にデータベースからデータをロードし、新しいデータがあるかどうかを確認する必要があるため、pullメソッドへのすべての呼び出しには@Transactionalアノテーションが付けられます。そうです、待機中の遅延結果を作成する必要がないためです。最初に受信したデータをデータベースに保存し、次にその値を待機中の結果に設定する必要があるため、プッシュメソッドも@Transactionでアノテーションを付ける必要があります。私のデータにはPostgresを使用しています。

11
Denis Stephanov

ここでの問題は、データベースプールの接続が不足していることのようです。

メソッドに@Transactionのタグが付けられていますが、コントローラーはメソッドの結果、つまりスレッドが解放されるようにDeferredResultができるだけ早く配信されることも期待しています。

さて、これはあなたがリクエストを実行したときに起こることです:

  • @Transaction機能はSpringプロキシに実装されており、接続を開き、サブジェクトメソッドを呼び出してから、トランザクションをコミットまたはロールバックする必要があります。
  • したがって、コントローラーがfooService.pullメソッドを呼び出すとき、実際にはプロキシを呼び出しています。
  • プロキシは最初にプールからの接続を要求する必要があり、次にサービスメソッドを呼び出します。サービスメソッドはそのトランザクション内でデータベース操作を実行します。最後に、トランザクションをコミットまたはロールバックし、最後に接続をプールに戻す必要があります。
  • このすべての後、メソッドはDeferredResultを返します。これは、コントローラーに渡されて返されます。

ここで問題となるのは、DeferredResultが非同期で使用されるように設計されていることです。つまり、promiseは後で他のスレッドで解決されることが期待されており、リクエストスレッドをできるだけ早く解放することになっています。

実際、 DeferredResult に関するSpringのドキュメントには次のように書かれています。

@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// From some other thread...
deferredResult.setResult(data);

コードの問題は、DeferredResultが同じリクエストスレッドで解決されていることです。

つまり、Springプロキシがデータベースプールへの接続を要求すると、高負荷テストを実行すると、多くの要求でプールがいっぱいになり、使用可能な接続がないことがわかります。そのため、リクエストは保留になりますが、その時点ではDeferredResultはまだ作成されていないため、タイムアウト機能は存在しません。

リクエストは基本的に、データベースプールからの接続が利用可能になるのを待っています。したがって、5秒が経過すると、要求は接続を取得し、コントローラーが応答を処理するために使用するDeferredResultを取得するとします。最終的に、5秒後にタイムアウトします。したがって、プールからの接続を待機する時間と、DeferredResultが解決されるのを待機する時間を追加する必要があります。

そのため、JMeterでテストすると、データベースプールから接続が枯渇するにつれて、要求時間が徐々に増加することがわかります。

次のapplication.propertiesファイルを追加することで、スレッドプールのログを有効にできます。

logging.level.com.zaxxer.hikari=DEBUG

データベースプールのサイズを構成し、Java Mission Control:から監視できるようにJMXサポートを追加することもできます。

spring.datasource.hikari.maximumPoolSize=10
spring.datasource.hikari.registerMbeans=true

JMXサポートを使用すると、データベースプールがどのように枯渇するかを確認できます。

ここでの秘訣は、promiseを解決するロジックを別のスレッドに移動することです。

@Override
public DeferredResult pull(Long previousId, String username) {


    DeferredResult result = createPollingResult(previousId, username);

    CompletableFuture.runAsync(() -> {
        //this is where you encapsulate your db transaction
        List<MessageDTO> messages = messageService.findRecents(previousId, username); // should be final or effective final
        if (messages.isEmpty()) {
           pollingResults.putIfAbsent(username, result);
        } else {
           result.setResult(messages);
        }
    });

    return result;
}

これを行うことにより、DeferredResultがすぐに返され、Springはその貴重なTomcatスレッドを解放しながら、非同期リクエスト処理の魔法を実行できます。

2
Edwin Dalorzo

多くの人が言ったように、それはパフォーマンスをテストする正しい方法ではありません。 XMLHttpRequestで行っていたように、特定の期間に自動化されたリクエストを行うように依頼しました。 intervalObservableは次のように使用できます。

「rxjs/Observable」から{Observable}をインポートします。

「rxjs/Subscription」から{Subscription}をインポートします。

private _intervalSubscription: Subscription;

ngOnInit() {
    this._intervalSubscription = Observable.interval(500).subscribe(x => {
        this.getDataFromServer();
    });
}

ngOnDestroy(): void {
    this._intervalSubscription.unsubscribe();
}

getDataFromServer() {
    // do your network call
    this.http.get<any>(this.url, {params})
                .subscribe((data) => {
                    console.log('s', data);
                }, (error) => {
                    console.log('e', error);
                }); 
}

これは、クライアント側からポーリングを実行するための最良の方法です。

編集1

private prevRequestTime: number;

ngAfterViewInit(): void {
    this.getDataFromServer();
}

getDataFromServer() {
    this.prevRequestTime = Date.now();
    // do your network call
    this.http.get<any>(this.url, {params})
            .subscribe((data) => {
                console.log('s', data);
                this.scheduleRequestAgain();
            }, (error) => {
                console.log('e', error);
                this.scheduleRequestAgain();
            }); 
}

scheduleRequestAgain() {
    let diff = Date.now() - this.prevRequestTime;
    setTimeout(this.getDataFromServer(), diff);
}
1

生産者と消費者の構造モデルが必要だと思います。私はあなたのためにコードを書きます。お役に立てば幸いです。

これはサンプルコードです:

DeferredResultStrore

@Component
public class DeferredResultStrore {

    private Queue<DeferredResult<String>> responseBodyQueue;
    private HashMap<String, List<DeferredResult<InterfaceModel>>> groupMap;
    private final long resultTimeOut;

    public DeferredResultStrore() {
        responseBodyQueue = new LinkedBlockingQueue<DeferredResult<String>>();
        groupMap = new HashMap<String, List<DeferredResult<InterfaceModel>>>();
        // write time.
        resultTimeOut = 1000 * 60 * 60;
    }

    public Queue<DeferredResult<String>> getResponseBodyQueue() {
        return responseBodyQueue;
    }

    public HashMap<String, List<DeferredResult<InterfaceModel>>> getGroupMap() {
        return groupMap;
    }

    public long getResultTimeOut() {
        return resultTimeOut;
    }

}

DeferredResultService

public interface DeferredResultService {

    public DeferredResult<?> biteResponse(HttpServletResponse resp, HttpServletRequest req);

    public DeferredResult<?> biteGroupResponse(String key, HttpServletResponse resp);

}

DeferredResultServiceImpl

@Service
public class DeferredResultServiceImpl implements DeferredResultService {

    @Autowired
    private DeferredResultStrore deferredResultStore;

    @Override
    public DeferredResult<?> biteResponse(final HttpServletResponse resp, HttpServletRequest req) {

        final DeferredResult<String> defResult = new DeferredResult<String>(deferredResultStore.getResultTimeOut());

        removeObserver(resp, defResult, null);

        deferredResultStore.getResponseBodyQueue().add(defResult);

        return defResult;
    }

    @Override
    public DeferredResult<?> biteGroupResponse(String key, final HttpServletResponse resp) {

        final DeferredResult<InterfaceModel> defResult = new DeferredResult<InterfaceModel>(
                deferredResultStore.getResultTimeOut());

        List<DeferredResult<InterfaceModel>> defResultList = null;

        removeObserver(resp, defResult, key);

        if (deferredResultStore.getGroupMap().containsKey(key)) {

            defResultList = deferredResultStore.getGroupMap().get(key);
            defResultList.add(defResult);

        } else {

            defResultList = new ArrayList<DeferredResult<InterfaceModel>>();
            defResultList.add(defResult);
            deferredResultStore.getGroupMap().put(key, defResultList);

        }

        return defResult;
    }

    private void removeObserver(final HttpServletResponse resp, final DeferredResult<?> defResult, final String key) {

        defResult.onCompletion(new Runnable() {
            public void run() {
                if (key != null) {
                    List<DeferredResult<InterfaceModel>> defResultList = deferredResultStore.getGroupMap().get(key);

                    if (defResultList != null) {
                        for (DeferredResult<InterfaceModel> deferredResult : defResultList) {
                            if (deferredResult.hashCode() == defResult.hashCode()) {
                                defResultList.remove(deferredResult);
                            }
                        }
                    }

                } else {
                    if (!deferredResultStore.getResponseBodyQueue().isEmpty()) {
                        deferredResultStore.getResponseBodyQueue().remove(defResult);
                    }
                }
            }
        });

        defResult.onTimeout(new Runnable() {
            public void run() {
                // 206
                resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

                if (key != null) {
                    List<DeferredResult<InterfaceModel>> defResultList = deferredResultStore.getGroupMap().get(key);

                    if (defResultList != null) {
                        for (DeferredResult<InterfaceModel> deferredResult : defResultList) {
                            if (deferredResult.hashCode() == defResult.hashCode()) {

                                InterfaceModel model = new InterfaceModel();
                                model.setId(key);
                                model.setMessage("onTimeout");

                                deferredResult.setErrorResult(model);
                                defResultList.remove(deferredResult);
                            }
                        }
                    }

                } else {
                    defResult.setErrorResult("onTimeout");
                    deferredResultStore.getResponseBodyQueue().remove(defResult);
                }
            }
        });
    }

}

PushService

public interface PushService {

    public boolean pushMessage(String message);

    public boolean pushGroupMessage(String key, String topic, HttpServletResponse resp);

}

PushServiceImpl

@Service
public class PushServiceImpl implements PushService {

    @Autowired
    private DeferredResultStrore deferredResultStore;

    @Override
    public boolean pushMessage(String message) {

        if (!deferredResultStore.getResponseBodyQueue().isEmpty()) {

            for (DeferredResult<String> deferredResult : deferredResultStore.getResponseBodyQueue()) {

                deferredResult.setResult(message);
            }

            deferredResultStore.getResponseBodyQueue().remove();
        }

        return true;
    }

    @Override
    public boolean pushGroupMessage(String key, String topic, HttpServletResponse resp) {
        List<DeferredResult<InterfaceModel>> defResultList = null;

        // select data in DB. that is sample group Push service. need to connect db.
        InterfaceModel model = new InterfaceModel();
        model.setMessage("write group message.");
        model.setId(key);

        if (deferredResultStore.getGroupMap().containsKey(key)) {
            defResultList = deferredResultStore.getGroupMap().get(key);

            for (DeferredResult<InterfaceModel> deferredResult : defResultList) {
                deferredResult.setResult(model);
            }

            deferredResultStore.getGroupMap().remove(key);
        }

        return true;
    }

}

InterfaceModel

public class InterfaceModel {

    private String message;

    private int idx;
    private String id;

    // DB Column

    public InterfaceModel() {
        // TODO Auto-generated constructor stub
    }

    public InterfaceModel(String message, int idx, String id) {
        this.message = message;
        this.idx = idx;
        this.id = id;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public int getIdx() {
        return idx;
    }

    public void setIdx(int idx) {
        this.idx = idx;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

}

web.xml

非同期-設定で非常に重要なサポート。

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>

Javaベース

@Bean
public ServletRegistrationBean dispatcherServlet() {
    ServletRegistrationBean registration = new ServletRegistrationBean(
            new DispatcherServlet(), "/");
    registration.setAsyncSupported(true);
    return registration;
}

実際には :

DeferredResultは、オープンリクエストに関連付けられています。リクエストが完了すると、DeferredResultがマップから削除され、クライアントは新しい長いポーリングリクエストを発行します。これにより、新しいDeferredResultインスタンスが追加されます。


Spring Bootは、アプリケーションコンテキスト内のサーブレットBeanをサーブレットコンテナに自動的に登録します。デフォルトでは、サポートされている非同期はtrueに設定されているため、サーブレット用のBeanを作成する以外に何もする必要はありません。

@ Aligtor、for you => public @interface EnableAsync SpringのXML名前空間にある機能と同様に、Springの非同期メソッド実行機能を有効にします。

1
Byeon0gam