Вызовите асинхронную службу, которая возвращает DeferredResults, несколько раз без увеличения времени выполнения.

мои приложения должны иметь 2 основные конечные точки: push и pull для отправки и получения данных.

Операция pull должна работать асинхронно и давать результат DeferredResult. Когда пользователь вызывает службу извлечения поверх остальных, новый DefferedResult создается и сохраняется в Map<Long, DefferedResult> results = new ConcurrentHashMap<>(), где ожидает новых данных или до истечения времени ожидания.

Операция push также вызывает пользователя поверх остальных, и эта операция проверяет карту результатов для получателя данных, отправленных этой операцией. Когда карта содержит результат получателя, эти данные устанавливаются в его результат, возвращается 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. Таким образом, каждый вызов вытягивания занимает не более 5 секунд, как я установил в DefferedResult, но это не так.

Как вы можете видеть на изображении, если я немедленно вызову остальную часть операции извлечения из моего javascript-клиента несколько раз, вы увидите, что задачи будут выполняться последовательно, а не одновременно. Задачи, которые я запускал в последний раз, занимают около 25 секунд, но мне нужно, чтобы когда 1000 пользователей одновременно выполняли операцию извлечения, эта операция должна занимать максимум 5 секунд + задержка.

введите здесь описание изображения

Как настроить мое приложение для одновременного выполнения этих задач и гарантировать, что каждая задача будет занимать около 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 потоках и вот результат:

введите здесь описание изображения

Как видите, запросы будут обрабатываться на 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.

ИЗМЕНИТЬ 3:

Я узнаю, когда я комментирую также @Transactional все работает нормально! Итак, можете ли вы сказать мне, как настроить весенние транзакции для большого количества операций без увеличения задержки?

Я добавил spring.datasource.maximumPoolSize=1000, чтобы увеличить размер пула, что, как я полагаю, должно быть, поэтому единственная проблема заключается в том, как ускорить методы с помощью @Transactional.

Каждый вызов метода pull аннотируется @Transactional, потому что мне нужно сначала загрузить данные из базы данных и проверить, есть ли новые данные, потому что да, мне не нужно создавать отложенный результат ожидания. методы push также должны быть аннотированы с помощью @Transaction, потому что мне нужно сначала сохранить полученные данные в базе данных, а затем установить это значение для ожидающих результатов. Для моих данных я использую Postgres.


person Denis Stephanov    schedule 27.05.2018    source источник
comment
Пробовал, результат тот же :/   -  person Denis Stephanov    schedule 29.05.2018
comment
может проблема не в бэкенде java. браузер может открыть ограниченное количество подключений к серверу. 2-6. число зависит от браузера. поэтому, даже если вы сделали 1000 запросов, возможно, они стоят в очереди в самом браузере. Просто для проверки попробуйте увеличить это значение. в параметре конфигурации Firefox есть network.http.max-persistent-connections-per-server. Вы также можете попробовать использовать несколько браузеров на нескольких компьютерах. или вы можете выйти из браузера и использовать какой-нибудь клиент npm.   -  person gagan singh    schedule 30.05.2018
comment
Вы не можете проверить задержку через тот же браузер. Это неправильный способ тестирования. Браузер будет иметь не более 6 подключений к данному домену по HTTP1.1, поэтому, если вы откроете 100, все они будут поставлены в очередь и выполнены, когда соединение станет доступным. Таким образом, ваш подход к тестированию неверен. Откройте несколько окон в режиме инкогнито, а затем попробуйте проверить свой подход.   -  person Tarun Lalwani    schedule 30.05.2018
comment
@TarunLalwani Хорошо, я попробовал несколько окон в режиме инкогнито, и в каждом окне я открываю свое приложение, которое вызывает метод pull, но в последнем окне эта операция также занимает около 25 секунд, так что я думаю, проблема в другом: /   -  person Denis Stephanov    schedule 30.05.2018
comment
Предоставьте минимальный репозиторий git, и я буду копать   -  person Tarun Lalwani    schedule 30.05.2018
comment
@TarunLalwani спасибо за ваши усилия, я попытался создать минимальный рабочий код, но обнаружил, что он работает, когда я вызываю эту службу с интервалом несколько раз. Поэтому я думаю, что проблема, возможно, связана с моим клиентским приложением, и я немного меняю вопрос и показываю, как называется моя служба.   -  person Denis Stephanov    schedule 30.05.2018
comment
вы можете создать простой тест junit и выполнить его в нескольких потоках, чтобы вы могли убедиться, что ваш код на стороне сервера в порядке, пока вам нужно исследовать на стороне клиента   -  person Angelo Immediata    schedule 30.05.2018
comment
Здесь нам не хватает кода внешнего интерфейса. Как вы вызываете службу/метод, который выполняет http-вызов?   -  person maxime1992    schedule 30.05.2018
comment
@ maxime1992 Привет, я добавил, как я называю этот REST в angular, проверьте, пожалуйста, раздел EDIT в конце сообщения.   -  person Denis Stephanov    schedule 30.05.2018
comment
Я видел этот раздел. Мне интересно, откуда вы это называете. Здесь нет петли   -  person maxime1992    schedule 30.05.2018
comment
@ maxime1992 Я просто добавляю вызов этой функции к кнопке, поэтому я просто нажимаю кнопку много раз   -  person Denis Stephanov    schedule 30.05.2018
comment
Как я и Тарун упомянули, ваш способ тестирования неверен. Просто напишите какой-нибудь тестовый пример junit без браузера с несколькими потоками (1000, если хотите). Все хорошо, кроме вашего способа тестирования.   -  person gagan singh    schedule 30.05.2018
comment
@gagansingh, не могли бы вы подсказать, как проверить время выполнения нескольких вызовов какой-либо службы?   -  person Denis Stephanov    schedule 31.05.2018
comment
Просто чтобы убедиться, что это не ошибка тестирования, я предполагаю, что вы хорошо осведомлены о том, что вызов вашей конечной точки pull будет заблокирован до тех пор, пока соответствующий результат не будет предоставлен вашей конечной точкой push или тайм-аутом потока tomcat. Таким образом, совершенно нормально видеть, что эта продолжительность зависит от того, как скоро вы предоставляете результаты на общей карте. Дело не в том, насколько быстро работает приложение. Вопрос в том, насколько быстро вы можете обеспечить результаты.   -  person Edwin Dalorzo    schedule 01.06.2018
comment
@EdwinDalorzo Я также попробовал Mozila с дополнительными настройками, которые я дал здесь в комментарии, и получил тот же результат. То, что вы говорите, имеет смысл, но я понятия не имею, как это сделать без карты. Мне нужно хранить ожидающие запросы (отложенные результаты) на карте, и когда сервер получает новые данные, я проверяю карту, есть ли какой-то запрос, ожидающий этих данных, и устанавливаю его, чтобы отложенный результат был отправлен клиенту, и я могу удалить это с карты.   -  person Denis Stephanov    schedule 01.06.2018
comment
@DenisStephanov Может быть и так, но это был бы другой вопрос. Ваш текущий вопрос касается производительности тестирования, ваши комментарии теперь касаются дизайна вашего решения.   -  person Edwin Dalorzo    schedule 01.06.2018
comment
@EdwinDalorzo нет, мой вопрос касается конфигурации, которая фиксирует увеличение времени запросов, я также включил его в подсказку о награде.   -  person Denis Stephanov    schedule 01.06.2018
comment
@DenisStephanov Я уже ответил на это, и я считаю, что это причина тех времен, которые вы видите. Но я понимаю, как я читал ваш код, что это ваш собственный дизайн и то, как вы тестируете.   -  person Edwin Dalorzo    schedule 01.06.2018
comment
@DenisStephanov В вашей службе также есть но, когда она обрабатывает запросы для одного и того же идентификатора. Я комментирую это в своем ответе, и это может быть причиной длительного ожидания, которое вы видите.   -  person Edwin Dalorzo    schedule 01.06.2018
comment
Я также поместил это в подсказку о награде. - Это не поможет ... если у вас уже есть настоящий ответ, и вы не хотите / не верите / не хотите его принимать. В чем здесь дело, я думаю.   -  person Stephen C    schedule 02.06.2018
comment
Я думаю, что ваша медленная обработка вызвана @Transactional   -  person Martin Ondo-Eštok    schedule 02.06.2018
comment
@MartinOndo-Eštok да, вы правы, но как я могу это исправить?? :/   -  person Denis Stephanov    schedule 02.06.2018
comment
В вашем вопросе не очевидно, где и как вы используете транзакции. кажется, что в вашем пуле соединений Hikari заканчиваются соединения. Возможно, количество одновременных запросов, которые вы делаете, достаточно велико, чтобы исчерпать подключения к базе данных в вашем пуле до такой степени, что новые запросы помещаются в очередь и, в конечном итоге, истекают по тайм-ауту и ​​вызывают это исключение.   -  person Edwin Dalorzo    schedule 02.06.2018


Ответы (3)


Кажется, проблема здесь в том, что у вас заканчиваются соединения в пуле базы данных.

У вас есть метод, помеченный @Transaction, но ваш контроллер также ожидает результат метода, то есть DeferredResult, который будет доставлен как можно скорее, чтобы поток был освобожден.

Вот что происходит, когда вы запускаете запрос:

  • Функциональность @Transaction реализована в прокси-сервере Spring, который должен открыть соединение, вызвать метод объекта, а затем зафиксировать или откатить транзакцию.
  • Следовательно, когда ваш контроллер вызывает метод fooService.pull, он фактически вызывает прокси.
  • Прокси-сервер должен сначала запросить соединение из пула, затем он вызывает ваш метод службы, который в рамках этой транзакции выполняет некоторую операцию с базой данных. Наконец, он должен зафиксировать или откатить транзакцию и, наконец, вернуть соединение в пул.
  • После всего этого ваш метод возвращает DeferredResult, который затем передается контроллеру для возврата.

Теперь проблема в том, что DeferredResult разработан таким образом, что его следует использовать асинхронно. Другими словами, ожидается, что обещание будет разрешено позже в каком-то другом потоке, и мы должны освободить поток запроса как можно скорее.

Фактически, документация Spring по DeferredResult говорит:

@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

Вы также можете настроить размер пула базы данных и даже добавить некоторую поддержку JMX, чтобы вы могли отслеживать его из Java Mission Control:

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

Используя поддержку JMX, вы сможете увидеть, как истощается пул базы данных.

Хитрость здесь заключается в перемещении логики, которая разрешает промис, в другой поток:

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

person Edwin Dalorzo    schedule 01.06.2018
comment
Этот идентификатор пользователя, который является ключом карты, является идентификатором зарегистрированного пользователя, и он всегда должен быть только один в приложении. - person Denis Stephanov; 02.06.2018
comment
Пожалуйста, проверьте обновленный вопрос. Я использую другую форму тестирования, а также вручную проверяю уникальный идентификатор userId. Мои запросы обрабатываются на 10, поэтому в приложении есть некоторые ограничения, которые, я думаю, можно установить. - person Denis Stephanov; 02.06.2018
comment
У одного парня в commed есть хорошее замечание, это вызвано @Transactional, можете ли вы сказать мне, как это исправить и сохранить эти аннотации ниже методов? - person Denis Stephanov; 02.06.2018
comment
Вы уже отредактировали свой вопрос 3 раза, что означает, что мы все здесь пытаемся ответить на неправильный вопрос с самого начала, и вы еще даже не выразили никакой признательности за усилия, чтобы помочь вам до сих пор. Итак, я думаю, что вам, вероятно, следует сесть и более тщательно изучить вашу проблему самостоятельно, и вернуться, когда у вас возникнет реальный вопрос, который вы хотите задать. С моей точки зрения, этот вопрос больше не годится, поскольку он повсюду и кажется невозможным узнать, что происходит с вашим кодом, исходя из того, чем вы делитесь. - person Edwin Dalorzo; 02.06.2018
comment
Я сожалею об этом, но я также пытаюсь решить эту проблему, и когда я нахожу что-то, что может более точно указать проблему, я редактирую вопрос. Я не могу удалить вопрос после новой находки из-за существующих ответов и текущей награды. Так что единственное, что я могу сделать, это просто обновить вопрос. Вы можете мне доверять, я трачу много времени на то, чтобы решить это самостоятельно, прежде чем спрашивать здесь. - person Denis Stephanov; 02.06.2018
comment
Я понимаю. Но вы также должны понимать, что написание моего ответа заняло у меня некоторое время. Я нашел время, чтобы изучить ваш вопрос, написал ваш код и запустил его. Вы даже не проголосовали за какой-либо ответ и постоянно меняете свои знания о проблеме, делая любые наши усилия устаревшими, и мы, ответчики, ничего не получаем от этого. У вас есть награда, мы все здесь для этого. Кстати, ваша транзакционная проблема, похоже, заключается в том, что ваш пул соединений Hikari слишком мал для количества запросов, которые вы хотите сделать. У него кончаются соединения. Сделайте его больше! - person Edwin Dalorzo; 02.06.2018
comment
Я знаю, я также был слишком занят решением своей проблемы, поэтому я забыл об этом до сих пор. Но будь уверен, я сделаю это позже. - person Denis Stephanov; 02.06.2018
comment
Да, это имеет смысл, но когда я увеличиваю это значение, оно будет работать до тех пор, пока я не прокомментирую аннотации @Transactional, после этого я раскомментирую его, и оно снова будет медленным. - person Denis Stephanov; 02.06.2018
comment
Извините, но я не понимаю вашего последнего комментария. Что происходит, когда вы увеличиваете значение? - person Edwin Dalorzo; 02.06.2018
comment
Извините за мой английский, я знаю, что он не самый лучший. Я расширил свое последнее редактирование и добавил больше информации о транзакциях в коде. Последняя похвала означает, что я могу исправить это свойство добавления исключения, чтобы увеличить размер пула, что я и сделал. Но это просто устраняет исключение, а не основную проблему замедления методов обработки, аннотированных @Transactional. - person Denis Stephanov; 02.06.2018
comment
Мы можем поговорить об этом, если хочешь. Возможно, вместе мы сможем найти ответ. Не стесняйтесь переместить это в чат, и я буду там. - person Edwin Dalorzo; 02.06.2018
comment
Давайте продолжим обсуждение в чате. - person Denis Stephanov; 02.06.2018

Как уже упоминали многие ребята, это неправильный способ проверить производительность. Вы просили делать автоматические запросы в определенный период времени, как вы делали это в XMLHttpRequest. Вы можете использовать interval из Observable как:

импортировать {Observable} из rxjs/Observable;

импортировать {Subscription} из 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);
                }); 
}

Это наилучший способ проведения опроса со стороны клиента.

ИЗМЕНИТЬ 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);
}
person Anshuman Jaiswal    schedule 01.06.2018
comment
Мой опрос зависит от данных сервера, я не уверен, но я думаю, что когда клиент управляет временем, когда вызывается служба, это короткий опрос, и когда клиент просто отправляет запрос на сервер и ждет там, пока новые данные (это мой случай) это называется long-polling. Но, может быть, я ошибаюсь. - person Denis Stephanov; 01.06.2018
comment
вы хотите установить его на интервал в зависимости от времени первого запроса с сервера? предположим, вы нажали первый запрос, и это занимает около 20 секунд, поэтому вы хотите сделать опрос с интервалом 20 секунд (т. е. дальнейшие запросы должны попадать на сервер через 20 секунд inetrval)? - person Anshuman Jaiswal; 01.06.2018
comment
Также, если вы хотите уведомлять клиента всякий раз, когда данные обновляются на сервере, вы можете сделать это двумя способами: 1) веб-сокеты 2) опрос. Вы можете оптимизировать опрос, отправив какую-либо версию от клиента, например, если у него уже есть последние данные, нет необходимости отправлять данные снова, и если он не отправляет последние данные с версией. - person Anshuman Jaiswal; 01.06.2018
comment
да, у меня есть какой-то постоянный тайм-аут (например, 5 секунд, в реальном приложении это будет около 30 секунд), и запрос ожидает на сервере, пока не будет что отправить. Если сервер получает новые данные, подходящие для ожидающего запроса, установите эти данные в качестве ответа, если нет новых данных и истекло время ожидания, отправьте просто пустой объект или ноль, и после этого клиент отправляет еще один запрос, и процесс повторяется. Таким образом, время запроса может быть от 0 с (новые данные на сервере при отправке запроса) до 30 с (нет данных - тайм-аут). В моем примере просто вручную вызовите запрос отправки опроса, чтобы проверить, будет ли он работать для многих пользователей. - person Denis Stephanov; 01.06.2018
comment
Оптимизация будет следующим шагом, но сейчас мне нужен работающий опрос в текущей версии без увеличения времени выполнения при отправке большего количества запросов. - person Denis Stephanov; 01.06.2018
comment
@DenisStephanov Я обновил ответ на EDIT 1. Теперь первый запрос будет в AfterViewInit, а затем он проверит время завершения запроса и на основе этого решит, когда сделать следующий вызов запроса. Он также будет обрабатывать тайм-аут сети, чтобы сделать следующий запрос в случае тайм-аута с сервера. - person Anshuman Jaiswal; 01.06.2018
comment
Спасибо, но я думаю, что в данном случае с моим клиентом проблем нет, потому что мне нужно решить, почему мои запросы не выполняются одновременно - person Denis Stephanov; 01.06.2018

Я думаю, вам нужна модель структуры производителя и потребителя. я пишу код для вас. Я надеюсь, что это поможет вам.

Это пример кода:

Строй отложенных результатов

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

}

Служба отложенных результатов

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

}

Модель интерфейса

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 очень важна в настройках.

<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 автоматически зарегистрирует любые компоненты сервлета в контексте вашего приложения с контейнером сервлета. По умолчанию для поддерживаемого асинхронного режима установлено значение true, поэтому вам не нужно ничего делать, кроме создания bean-компонента для вашего сервлета.

@Aligtor, для вас => public @interface EnableAsync Включает возможность асинхронного выполнения методов Spring, аналогичную функциональности, найденной в пространстве имен XML Spring.

person 0gam    schedule 01.06.2018
comment
Привет, спасибо за ответ, я попробую сегодня и дам вам ответ. Не могли бы вы предоставить конфигурацию последнего блока кода на основе Java? - person Denis Stephanov; 01.06.2018
comment
Есть много кода, который, я думаю, не помогает OP. Почему вы так регистрируете компонент сервлета? Я думаю, что он загружается вместо вас, а также для асинхронного поведения вы можете использовать @EnableAsync - person Aligator; 01.06.2018
comment
@ Byeon0gam Я попробовал ваш код, но получил сообщение Не удалось открыть ресурс ServletContext [/WEB-INF/dispatcherServlet-servlet.xml ... так что с этой конфигурацией что-то не так, для нее требуется некоторый xml, но я не использую его в своем приложение - person Denis Stephanov; 01.06.2018
comment
@Aligtor, хорошо, я знаю ... я просто даю вам (Денис Стефанов) базовую конфигурацию Java. - person 0gam; 02.06.2018
comment
@DenisStephanov, ваше приложение загружается. По умолчанию для поддерживаемого асинхронного режима установлено значение true. Пожалуйста, попробуйте еще раз - person 0gam; 02.06.2018