У меня возникла странная, но серьезная проблема с несколькими (около 15) экземплярами веб-приложений Java EE-ish (Hibernate 4+ Spring + Quartz + JSF + Facelets + Richfaces) на Tomcat 7/Java 7.
Система работает очень хорошо, но после очень большого количества времени все экземпляры приложения в то же время внезапно страдают от возрастания времени отклика. В основном приложение по-прежнему работает, но время отклика примерно в три раза выше.
Это две диаграммы, отображающие время отклика двух коротких рабочих процессов/действий (вход в систему, список доступа к семинарам, ajax-обновление этого списка, выход из системы, нижняя строка - это просто время запроса для обновления ajax) два примера экземпляра приложения:
Как вы можете видеть, оба экземпляра приложения "взрываются" в одно и то же время и остаются медленными. После перезапуска сервера все возвращается к норме. Все экземпляры приложения "взрываются" одновременно.
Мы сохраняем данные сеанса в базе данных и используем это для кластеризации. Мы проверили размер и номер сеанса, и оба они довольно низки (что означает, что на других серверах с другими приложениями мы иногда имеем больше и больше сеансов). Другой Tomcat в кластере обычно остается быстрым в течение еще нескольких часов, и после этого случайного количества времени он также "умирает". Мы проверили размеры кучи с помощью jconsole, а основная куча оставалась между 2.5 и 1 ГБ, пул соединений db в основном полон бесплатных соединений, а также пулов потоков. Максимальный размер кучи составляет 5 ГБ, а также имеется достаточно свободного пространства. Нагрузка не особенно высока; всего около 5% нагрузки на основной процессор. Сервер не заменяет. Это также не проблема оборудования, поскольку мы дополнительно развернули приложения для виртуальной машины, где проблемы остались прежними.
Я не знаю, где искать больше, я из идей. Есть ли у кого-то идея, где искать?
2013-02-21 Обновление: новые данные!
Я добавил еще два графика трассировки в приложение. Что касается измерения: система мониторинга вызывает сервлет, который выполняет две задачи, измеряет время выполнения для каждого на сервере и записывает время, принятое в качестве ответа. Эти значения регистрируются системой мониторинга.
У меня есть несколько интересных новых фактов: горячее передислоцирование приложения заставляет этот единственный экземпляр текущего Tomcat сходить с ума. Это также, по-видимому, влияет на производительность вычисления центрального процессора (см. Ниже). Этот индивидуальный контекст-взрыв отличается от общего контекста-взрыва, который происходит случайным образом.
Теперь для некоторых данных:
Сначала отдельные строки:
- Светло-голубой - это общее время выполнения небольшого рабочего процесса (подробности см. выше), измеренного на клиенте
- Красный - это "часть" светло-голубого цвета, и это время, затраченное на выполнение специального шага рабочего процесса, измеренного на клиенте.
- Синий цвет измеряется в приложении и состоит из чтения списка сущностей из БД через Спящий режим и итерации по этому списку, выборки ленивых коллекций и ленивых объектов.
- Green - это небольшой процессорный тест с использованием операций с плавающей точкой и целого числа. Насколько я вижу, нет выделения объектов, поэтому никакого мусора.
Теперь для отдельных этапов взрыва: я отметил каждое изображение тремя черными точками. Первый - это "небольшое" разброс в более или менее одном экземпляре приложения - в Inst1 он перескакивает (особенно заметен в красной строке), в то время как Inst2 ниже более или менее остается спокойным.
После этого небольшого взрыва произошел "большой взрыв", и все экземпляры приложения на этом Tomcat взорвались (2-я точка). Обратите внимание, что этот взрыв влияет на все операции высокого уровня (обработка запросов, доступ к БД), но не. Он остается низким в обеих системах.
После этого я перепрограммировал Inst1, коснувшись файла context.xml. Как я уже сказал ранее, этот экземпляр идет от взрыва до полного уничтожения (светло-голубая линия выходит из графика - она составляет около 18 секунд). Обратите внимание, как: а) это перераспределение не влияет на Inst2 вообще, и б) как не влияет на доступ к необработанному DB-интерфейсу Inst1, но как внезапно кажется, что ЦПУ стал медленнее!. Я говорю, это безумие.
Обновление обновленияСлушатель утечки Tomcat не скулит о старых потоках ThreadLocals или Threads, когда приложение не развернуто. Очевидно, что есть какая-то проблема очистки (которая, как я полагаю, напрямую не связана с Большим взрывом), но Tomcat не имеет для меня намека.
2013-02-25 Обновление: прикладная среда и расписание кварца
Среда приложения не очень сложна. Сетевые компоненты в стороне (я не знаю достаточно о них) там в основном один сервер приложений (Linux) и два сервера баз данных (MySQL 5 и MSSQL 2008). Основная загрузка - на сервере MSSQL, другая - только как место для хранения сеансов.
Сервер приложений запускает Apache как балансировщик нагрузки между двумя Tomcats. Итак, у нас есть два JVM, работающих на одном оборудовании (два экземпляра Tomcat). Мы используем эту конфигурацию, чтобы не балансировать нагрузку, поскольку сервер приложений способен просто запускать приложение (что было сделано уже много лет), но для включения небольших обновлений приложений без простоев. Соответствующее веб-приложение развертывается как отдельный контекст для разных клиентов, около 15 контекстов для Tomcat. (Мне кажется, что я смешал "экземпляры" и "контексты" в моей публикации - здесь, в офисе, они часто используются синонимом, и мы обычно волшебным образом знаем, что говорит коллега. Мой плохой, мне очень жаль.)
Чтобы прояснить ситуацию с лучшей формулировкой: диаграммы, которые я опубликовал, отображали время отклика двух разных контекстов одного и того же приложения на одной и той же JVM. "Большой взрыв" влияет на все контексты на одной JVM, но не происходит с другой (порядок, в котором Tomcats взрывается случайным образом). После горячей пересылки один контекст на одном экземпляре Tomcat сходит с ума (со всеми смешными побочными эффектами, такими как, казалось бы, более медленный процессор для этого контекста).
Общая нагрузка на систему довольно низкая. Это внутреннее программное обеспечение, связанное с основным бизнесом, с примерно 30 активными пользователями одновременно. Специфические запросы приложений (затрагивающие серверы) в настоящее время составляют около 130 в минуту. Количество одиночных запросов невелико, но сами запросы часто требуют нескольких сотен выборок в базе данных, поэтому они довольно дороги. Но обычно все вполне приемлемо. Приложение также не создает большие бесконечные кеши - некоторые данные поиска кэшируются, но только на короткий промежуток времени.
Выше я писал, что серверы, на которых можно запускать приложение просто отлично в течение нескольких лет. Я знаю, что лучший способ найти проблему - выяснить, когда все пошло не так в первый раз и посмотреть, что было изменено в этот таймфрейм (в самом приложении, связанных с ним библиотеках или инфраструктуре), однако проблема заключается в том, что мы не знаем, когда возникли проблемы. Просто позвоните на этот субоптимальный (в смысле отсутствия) мониторинг приложений...: -/
Мы исключили некоторые аспекты, но приложение обновлялось несколько раз в течение последних месяцев, и, таким образом, мы, например, не может просто развертывать более старую версию. Самым большим обновлением, которое не было внесением изменений, был переход от JSP к Facelets. Но все-таки "что-то" должно быть причиной всех проблем, но я понятия не имею, почему Facelets, например, должен влиять на время запросов БД.
Кварц
Что касается графика кварца: всего 8 заданий. Большинство из них работают только один раз в день и связаны с синхронизацией больших объемов данных (абсолютно не "большой", как в "больших больших данных", а просто больше, чем усредненный пользователь видит в своей обычной повседневной работе). Тем не менее, эти рабочие места, конечно, работают ночью, и проблемы возникают в дневное время. Я опускаю здесь подробный список вакансий (если полезен, я могу предоставить более подробную информацию). Исходный код рабочих мест не был изменен в течение последних месяцев. Я уже проверил, совпадают ли взрывы с рабочими местами, но результаты в лучшем случае неубедительны. Я бы сказал, что они не выравниваются, но поскольку есть несколько заданий, которые запускаются каждую минуту, я пока не могу это исключить. На мой взгляд, acutal-задания, которые работают каждую минуту, довольно малы, они обычно проверяют, доступны ли данные (в разных источниках, БД, внешние системы, учетная запись электронной почты), и если это так записать его в БД или нажать на другую систему.
Тем не менее, я в настоящее время разрешаю вести ведение независимого выполнения задания, чтобы я мог точно видеть начальную и конечную временную метку каждого отдельного выполнения задания. Возможно, это дает больше понимания.
2013-02-28 Обновление: фазы и время JSF
Я вручную добавил слушателя JSF phae в приложение. Я выполнил образец вызова (обновление ajax), и это то, что у меня есть (слева: обычный запуск экземпляра Tomcat, справа: экземпляр Tomcat после Big Bang - числа были взяты почти одновременно из обоих Tomcats и находятся в миллисекундах):
- RESTORE_VIEW: 17 против 46
- APPLY_REQUEST_VALUES: 170 против 486
- PROCESS_VALIDATIONS: 78 против 321
- UPDATE_MODEL_VALUES: 75 против 307
- RENDER_RESPONSE: 1059 против 4162
Само обновление ajax относится к форме поиска и ее результатам поиска. Там также задерживается еще одна задержка между фильтром внешнего запроса приложения и потоком веб-потока: там FlowExecutionListenerAdapter
, который измеряет время, затрачиваемое на определенных этапах веб-потока. Этот слушатель сообщает 1405 мс для "Запрошенного представления" (который, насколько я знаю, первое событие веб-потока) из общего числа 1632 мс для полного запроса на нерасширенный Tomcat, поэтому я оцениваю примерно 200 мс накладных расходов. < ш > Но на взорванном Tomcat он сообщает 5332 мс для запрошенного запроса (что означает, что все фазы JSF происходят за эти 5 секунд) из общей длительности запроса 7105 мс, таким образом, мы занимаем почти 2 секунды накладные расходы для всего, что было за пределами запроса веб-потока.
Ниже моего измерительного фильтра цепь фильтра содержит org.ajax4jsf.webapp.BaseFilter
, затем вызывается сервлет Spring.
2013-06-05 Обновление: все, что происходит в последние недели
Небольшое и довольно позднее обновление... производительность приложения по-прежнему отстойная через некоторое время, и поведение остается неустойчивым. Профилирование еще не помогло, оно просто породило огромное количество данных, которые трудно вскрыть. (Попробуйте зайти в данные о производительности или профилировать производственную систему... вздох). Мы провели несколько тестов (разрывая определенные части программного обеспечения, отказываясь от других приложений и т.д.) И фактически имели некоторые улучшения, которые влияют на все приложение. Режим флеша по умолчанию для нашего EntityManager
равен AUTO
, и при просмотре рендеринга выдается множество выборок и выборок, всегда включающих проверку необходимости промывки.
Таким образом, мы построили фазовый прослушиватель JSF, который устанавливает режим сброса на COMMIT
во время RENDER_RESPONSE
. Это значительно улучшило общую производительность и, похоже, немного смягчило проблемы.
Тем не менее, наш мониторинг приложений постоянно приводит к безумным результатам и производительности в некоторых контекстах на некоторых экземплярах tomcat. Как действие, которое должно завершиться в течение секунды (и это действительно происходит после развертывания), и теперь это занимает больше четырех секунд. (Эти номера поддерживаются ручной синхронизацией в браузерах, так что это не мониторинг, который вызывает проблемы).
См. следующий рисунок, например:
На этой диаграмме показаны два экземпляра tomcat, работающих в одном и том же контексте (что означает тот же самый дБ, та же конфигурация, тот же банд). Опять же, синяя линия - это время, затраченное чистыми операциями чтения БД (выбор списка объектов, их повторение, ленивые выборки и связанные с ними данные). Бирюзовая и красная линии измеряются путем рендеринга нескольких видов и выполнения обновления ajax, соответственно. Данные, полученные двумя запросами в бирюзовых и красных, в основном такие же, как и для синей линии.
Теперь около 0700 экземпляра 1 (справа) это огромное увеличение чистого времени БД, которое, похоже, влияет и на фактическое время отклика рендера, но только на tomcat 1. Tomcat 0 в значительной степени не зависит от этого, поэтому он не может быть вызван сервером БД или сетью с обоими кошками, работающими на одном физическом оборудовании. Это должно быть проблемой программного обеспечения в домене Java.
Во время моих последних тестов я узнал что-то интересное: все ответы содержат заголовок "X-Powered-By: JSF/1.2, JSF/1.2". Некоторые (ответы на перенаправление, созданные WebFlow) даже имеют "JSF/1.2" три раза там. Я проследил части кода, которые устанавливали эти заголовки, и первый раз, когда этот заголовок установлен, вызван этим стеком:
... at org.ajax4jsf.webapp.FilterServletResponseWrapper.addHeader(FilterServletResponseWrapper.java:384)
at com.sun.faces.context.ExternalContextImpl.<init>(ExternalContextImpl.java:131)
at com.sun.faces.context.FacesContextFactoryImpl.getFacesContext(FacesContextFactoryImpl.java:108)
at org.springframework.faces.webflow.FlowFacesContext.newInstance(FlowFacesContext.java:81)
at org.springframework.faces.webflow.FlowFacesContextLifecycleListener.requestSubmitted(FlowFacesContextLifecycleListener.java:37)
at org.springframework.webflow.engine.impl.FlowExecutionListeners.fireRequestSubmitted(FlowExecutionListeners.java:89)
at org.springframework.webflow.engine.impl.FlowExecutionImpl.resume(FlowExecutionImpl.java:255)
at org.springframework.webflow.executor.FlowExecutorImpl.resumeExecution(FlowExecutorImpl.java:169)
at org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:183)
at org.springframework.webflow.mvc.servlet.FlowController.handleRequest(FlowController.java:174)
at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:48)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:920)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:827)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
... several thousands ;) more
Во второй раз этот заголовок устанавливается
at org.ajax4jsf.webapp.FilterServletResponseWrapper.addHeader(FilterServletResponseWrapper.java:384)
at com.sun.faces.context.ExternalContextImpl.<init>(ExternalContextImpl.java:131)
at com.sun.faces.context.FacesContextFactoryImpl.getFacesContext(FacesContextFactoryImpl.java:108)
at org.springframework.faces.webflow.FacesContextHelper.getFacesContext(FacesContextHelper.java:46)
at org.springframework.faces.richfaces.RichFacesAjaxHandler.isAjaxRequestInternal(RichFacesAjaxHandler.java:55)
at org.springframework.js.ajax.AbstractAjaxHandler.isAjaxRequest(AbstractAjaxHandler.java:19)
at org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.createServletExternalContext(FlowHandlerAdapter.java:216)
at org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:182)
at org.springframework.webflow.mvc.servlet.FlowController.handleRequest(FlowController.java:174)
at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:48)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:920)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:827)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
Я не знаю, могло ли это указывать на проблему, но я не заметил этого с другими приложениями, которые работают на любом из наших серверов, поэтому это может также дать некоторые подсказки. Я действительно не знаю, что делает этот код рамки (по общему признанию, я еще не погрузился в него)... возможно, у кого-то есть идея? Или я бегу в тупик?
Приложение
Мой контрольный код процессора состоит из цикла, который вычисляет Math.tan и использует значение результата для изменения некоторых полей экземпляра сервлета (там нет волатильных/синхронизированных), а во-вторых выполняет несколько необработанных целочисленных вычислений. Это не сложно, я знаю, но хорошо... кажется, что-то показывает в чартах, однако я не уверен, что он показывает. Я обновляю поле, чтобы предотвратить HotSpot от оптимизации всего моего драгоценного кода;)
long time2 = System.nanoTime();
for (int i = 0; i < 5000000; i++) {
double tan = Math.tan(i);
if (tan < 0) {
this.l1++;
} else {
this.l2++;
}
}
for (int i = 1; i < 7500; i++) {
int n = i;
while (n != 1) {
this.steps++;
if (n % 2 == 0) {
n /= 2;
} else {
n = n * 3 + 1;
}
}
}
// This execution time is written to the client.
time2 = System.nanoTime() - time2;