Система не масштабируется для поддержки одновременных пользователей

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

Я запускаю веб-приложение Java, развернутое на (виртуальной) машине Ubuntu Quad Core с 16 ГБ ОЗУ. Я использую Apache Tomcat 7 и базу данных MySQl 5.5. Tomcat и MySQL используют настройки по умолчанию — я их никак не настраивал.

Я использую Apache Benchmark для запуска ряда тестов, которые в конечном итоге создают SQL-запрос для возврата одной строки данных, где размер ответа очень мал.

Я использую Spring JDBCTemplate и Apache Commons BasicDataSource. Конфигурация Spring bean показана ниже.

<!-- READ ONLY CONNECTION TO DATABASE -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <property name="driverClassName">
    <value>com.mysql.jdbc.Driver</value>
  </property>
  <property name="username">
    <value>${database.username}</value>
  </property>
  <property name="password">
    <value>${database.password}</value>
  </property>
  <property name="url">
    <value>${database.url}/${database.schema}</value>
  </property>
  <property name="timeBetweenEvictionRunsMillis" value="7200000" />
  <property name="minEvictableIdleTimeMillis" value="3600000" />
  <property name="maxActive" value="100" />
  <property name="maxIdle" value="5" />
  <property name="defaultAutoCommit" value="false" />
  <property name="defaultReadOnly" value="true" />
</bean>

<bean id="myDao" class="...">
   <property name="jdbcTemplate" ref="jdbcTemplate"></property>
   <property name="dataSource" ref="dataSource"></property>
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
   <property name="dataSource" ref="dataSource" />
</bean>

Мой метод Java, который создает пару запросов, помечен @Transactional.

Вот результаты моих тестов:

  • 1 запрос занимает 0,2 секунды.
  • 10 запросов (выполняемых одновременно) занимают 0,9 секунды.

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

Заранее спасибо,

Фил

Обновить

Дополнительные показатели:

  1 Request,  Concurrency   1 = 0.22s
 10 Requests, Concurrency  10 = 0.6s (mean), 0.5(min)
100 Requests, Concurrency 100 = 7 (mean), 3.7(min)
300 Requests, Concurrency 300 = 12s (mean), 4.3(min)
300 Requests, Concurrency 300 = 18s (mean), 6.4(min)

Размер ответа 1кб.

Попытка одинаковых запросов и изменение параллелизма:

300 Requests, Concurrency   8 = total time: 14.9s
300 Requests, Concurrency  20 = total time: 15.3s
300 Requests, Concurrency 300 = total time: 24s

Таким образом, уменьшение параллелизма до 8 завершает 10 секунд быстрее, чем параллелизм 300. Увеличение с 8 замедляет транзакции. 8 кажется наиболее оптимальным параллелизмом.


person Phil    schedule 27.11.2012    source источник
comment
Я бы сначала разобрался, на что именно уходит время. Рендеринг пользовательского интерфейса/или java-код, или SQL-запросы и т. д. Вы можете попробовать JAMON API для сбора статистики по различным частям кода, а затем принять решение о дальнейших шагах. JAMON очень прост в использовании, практически не требует обучения.   -  person Gopi    schedule 27.11.2012
comment
Я думаю, вам нужно иметь несколько больше, чем две точки данных, прежде чем сделать вывод, что эта связь является линейной... возможно, 10 000 одновременных запросов займут всего 1,2 секунды?   -  person eggyal    schedule 27.11.2012
comment
Попробуйте профилировать свое приложение, прежде чем догадываться, что в нем тормозит.   -  person Uwe Plonus    schedule 27.11.2012
comment
Хороший вопрос, эгьял. Я попытался упростить исходный вопрос, но добавлю дополнительные показатели.   -  person Phil    schedule 27.11.2012


Ответы (1)


Есть несколько вещей, которые следует учитывать при попытке сделать приложение параллельным.

Во-первых, тот факт, что ваш сервер имеет четыре ядра, не означает, что все они доступны для вашей JVM, вам нужно запросить среду выполнения, чтобы узнать, сколько из них доступно, и это также технически возможно изменить в течение срока службы JVM, хотя редко.

Далее необходимо рассмотреть физическую топологию вашей среды. БД работает на том же сервере, что и приложение? Если это так, у вас есть дополнительная конкуренция за ресурсы с точки зрения обработки и ввода-вывода, а не только того, что делает ваше приложение.

Как только вы поймете эти моменты, вам нужно рассмотреть профиль ввода-вывода и обработки вашего приложения. Например, приложение, которое находит простые числа и выводит их в системный журнал, фактически выполняет 100% обработку по сравнению с 0% операций ввода-вывода. В этом типе приложений нет смысла иметь больше потоков в вашем приложении, чем доступно ядер, поскольку ядра будут постоянно заняты тем, что они делают, и накладные расходы на переключение задач фактически замедлят ваше приложение.

Приложение, сильно привязанное к БД, обычно имеет относительно высокий профиль ввода-вывода для обработки, хотя этот профиль становится менее связанным с вводом-выводом, если вы только читаете, и эти чтения относительно малы с четко определенной базой данных, где запрашиваемые данные логически расположены . Размер БД также влияет на ввод-вывод, в зависимости от того, может ли весь набор БД храниться в памяти или происходит подкачка диска.

Я настоятельно рекомендую прочитать Java Concurrency in Practice Брайана Гетца, если вы новичок в параллелизме. При этом вы придерживаетесь разумного подхода, профилируя свое приложение таким, какое вы есть.

person codeghost    schedule 27.11.2012