web-dev-qa-db-fra.com

Appelez le service asynchrone qui renvoie DeferredResults plusieurs fois sans augmenter le temps d'exécution

mes applications doivent avoir 2 points de terminaison principaux: Push , pull pour l'envoi et la récupération de données. 

L'opération d'extraction doit fonctionner de manière asynchrone et entraîner un résultat DeferredResult. Lorsque le service d'appel d'un utilisateur est inutilisé, un nouveau DefferedResult est créé et stocké dans Map<Long, DefferedResult> results = new ConcurrentHashMap<>() où est en attente de nouvelles données ou jusqu'à ce que le délai expire.

Opération Push appelez également l'utilisateur au repos, et cette opération vérifie la mappe des résultats pour le destinataire des données envoyées par cette opération. Lorsque la carte contient le résultat du destinataire, ces données sont définies sur son résultat, DefferedResult est renvoyé.

Voici le code de base:

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

Le code fonctionne comme prévu. Le problème est que cela devrait également fonctionner pour plusieurs utilisateurs. J'imagine que le nombre maximum d'utilisateurs actifs appelant l'opération d'extraction tirera au maximum 1 000. Ainsi, chaque appel d'extraction prend au maximum 5 secondes lorsque je m'installe dans DefferedResult, mais ce n'est pas le cas.

Comme vous pouvez le voir sur l'image, si j'appelle plusieurs fois immédiatement le reste de l'opération d'extraction à partir de mon client javascript, vous pouvez voir que les tâches seront exécutées de manière séquentielle plutôt que simultanément. Les tâches que j’ai lancées en dernier prennent environ 25 secondes, mais j’ai besoin que, lorsque 1 000 utilisateurs exécutent en même temps une opération d’attraction, cette opération prenne au maximum 5 secondes + latence.

 enter image description here

Comment configurer mon application pour exécuter ces tâches simultanément et s'assurer que chaque tâche durera environ 5 secondes ou moins (lorsqu'un autre utilisateur envoie quelque chose à un utilisateur en attente)? J'ai essayé d'ajouter cette configuration dans le fichier de propriétés:

server.Tomcat.max-threads=1000

et aussi cette configuration:

@Configuration
public class AsyncConfig extends AsyncSupportConfigurer {

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

Mais ça n'a pas aidé, toujours le même résultat. Pouvez-vous m'aider à le configurer s'il vous plaît? Merci en conseil.

EDIT:

Voici comment j'appelle ce service d'angular:

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

Lorsque j'ai essayé de l'appeler plusieurs fois avec du code JS pur, comme ceci:

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);

il exécutera chaque appel de requête beaucoup plus rapidement (environ 7 secondes). Je m'attendais à ce que l'augmentation soit causée par un appel de base de données en service, mais cela reste meilleur que 25 secondes Ai-je quelque chose de mal à appeler ce service en mode angulaire?

EDIT 2:

J'ai essayé une autre forme de test et au lieu de navigateur, j'ai utilisé jMeter. J'exécute 100 requêtes dans 100 threads et voici le résultat:

 enter image description here

Comme vous pouvez le constater, les demandes sont traitées par 10, et après avoir atteint 50 demandes, une exception est générée:

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

Je commente également le code dans lequel j'utilise les référentiels pour m'assurer qu'il n'y a rien avec la base de données et le même résultat. De plus, j'ai défini un utilisateur unique pour chaque demande avec la classe AtomicLong.

EDIT 3:

Je découvre que lorsque je commente aussi @Transactional tout fonctionne bien! Alors pouvez-vous me dire comment régler les transactions de spring pour un grand nombre d'opérations sans augmenter les délais?

J'ai ajouté spring.datasource.maximumPoolSize=1000 pour augmenter la taille du pool, ce qui devrait, je suppose, donc le seul problème est de savoir comment accélérer les méthodes avec @Transactional.

Chaque appel à une méthode d'extraction est annoté avec @Transactional car je dois d'abord charger les données de la base de données et vérifier s'il y a de nouvelles données, car oui, je n'ai pas à créer de résultats différés en attente. Les méthodes push doivent également être annotées avec @Transaction, car il me faut tout d’abord stocker les données reçues dans la base de données et définir ensuite cette valeur sur les résultats en attente. Pour mes données, j'utilise Postgres.

10
Denis Stephanov

Il semble que le problème ici est que vous manquez de connexions dans le pool de bases de données.

Votre méthode est étiquetée avec @Transaction mais votre contrôleur s'attend également à ce que le résultat de la méthode, c.-à-d. La DeferredResult, soit fourni dès que possible, de sorte que le thread soit libéré.

Maintenant, voici ce qui se passe lorsque vous exécutez une demande:

  • La fonctionnalité @Transaction est implémentée dans un proxy Spring qui doit ouvrir une connexion, appeler votre méthode sujet, puis valider ou annuler la transaction.
  • Par conséquent, lorsque votre contrôleur appelle la méthode fooService.pull, il appelle en fait un proxy.
  • Le proxy doit d'abord demander une connexion au pool, puis il appelle votre méthode de service qui, dans cette transaction, effectue une opération de base de données. Enfin, il doit valider ou annuler la transaction et finalement renvoyer la connexion au pool.
  • Après tout cela, votre méthode retourne une DeferredResult, qui est ensuite transmise au contrôleur pour qu’elle le renvoie.

Le problème est que DeferredResult est conçu de manière à pouvoir être utilisé de manière asynchrone. En d'autres termes, la promesse devrait être résolue ultérieurement dans un autre thread, et nous sommes supposés libérer le thread de requête dès que possible.

En fait, la documentation Spring sur DeferredResult dit:

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

Le problème dans votre code est précisément que la DeferredResult est en cours de résolution dans le même thread de requête.

Le problème est donc que lorsque le proxy Spring demande une connexion au pool de bases de données, lorsque vous effectuez vos tests de charge lourde, de nombreuses requêtes constatent que le pool est saturé et qu'aucune connexion n'est disponible. La demande est donc mise en attente, mais à ce stade, votre DeferredResult n'a pas encore été créée. Par conséquent, sa fonctionnalité de délai d'attente n'existe pas.

Votre demande attend essentiellement qu'une connexion du pool de bases de données soit disponible. Donc, disons 5 secondes, la demande obtient une connexion et vous obtenez maintenant DeferredResult que le contrôleur utilise pour gérer la réponse. Finalement, 5 secondes plus tard, il expire. Donc, vous devez ajouter votre temps d'attente pour une connexion du pool et votre temps d'attente pour la résolution de DeferredResult

C'est pourquoi vous voyez probablement que, lorsque vous testez avec JMeter, le temps de requête augmente progressivement à mesure que les connexions s'épuisent du pool de bases de données.

Vous pouvez activer la journalisation pour le pool de threads en ajoutant le fichier application.properties suivant:

logging.level.com.zaxxer.hikari=DEBUG

Vous pouvez également configurer la taille de votre pool de bases de données et même ajouter un support JMX permettant de le surveiller à partir de Java Mission Control:

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

En utilisant le support JMX, vous pourrez voir comment le pool de bases de données s'épuise.

L'astuce consiste ici à déplacer la logique qui résout la promesse vers un autre thread:

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

En faisant cela, votre DeferredResult est renvoyée immédiatement et Spring peut effectuer sa magie de traitement des demandes asynchrones tout en libérant ce précieux thread Tomcat.

2
Edwin Dalorzo

Comme beaucoup de gars l'ont mentionné, ce n'est pas la bonne façon de tester les performances. Vous avez demandé à faire des demandes automatisées à une certaine période, comme vous le faisiez dans XMLHttpRequest. Vous pouvez utiliser interval sur Observable comme:

import {Observable} de "rxjs/Observable";

importer {Subscription} de "rxjs/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);
                }); 
}

C’est le meilleur moyen de procéder à une interrogation côté client.

EDIT 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
Anshuman Jaiswal

Je pense que vous avez besoin d'un modèle de structure de producteur et de consommateur. J'écris du code pour vous. J'espère que cela vous aidera.

Ceci est un exemple de code:

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

async pris en charge très important dans les paramètres.

<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>

Base Java

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

En réalité : 

Un DeferredResult est associé à une demande ouverte. Lorsque la demande Est terminée, le DeferredResult est supprimé de la carte, puis le client Émet une nouvelle demande d'interrogation longue qui ajoute une nouvelle instance DeferredResult.


Spring Boot enregistrera automatiquement tous les beans Servlet dans le contexte de votre application avec le conteneur de servlets. Par défaut, async pris en charge est défini sur true, il ne vous reste plus qu'à créer un bean pour votre Servlet.

@Aligtor, pour vous => public @interface EnableAsync Active la capacité d'exécution de méthode asynchrone de Spring, similaire à la fonctionnalité trouvée dans l'espace de noms XML de Spring.

1
Byeon0gam