LINUX.ORG.RU

Мониторинг проблем веб-приложений: зависшие запросы.

 , , ,


0

1

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

С чего все началось

У нас было legacy веб-приложение (dojo, tomcat, oracle db, написанное как молитва ЛММ: бизнес логика была в js, jsp, servlet, неких объектах прибитых к субд и, что самое приятное, в sql процедурах) которое более менее стабильно работало и приносило деньги, но в силу архитектуры его старались лишний раз не трогать. В один сумрачный понедельник оно зависло прямо на сервере у клиентов - кончились коннекты к субд, в логах причина не засветилась, приложение не трогали гдето полгода, так что ошибка очень нехорошая. С того сумрачного дня сумрачность только сгустилась - приложение стало зависать в случайное время, то ночью на следующий день после рестарта, то работает две недели.

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

Итак вернемся к нашему ПО. С очередного зависания нам удалось снят дамп потоков, он показал что зависает что-то внутри oracle, причем из-за архитектуры common-dbcp сей «вечный» запрос при некоторых условиях создает глобальную блокировку что вешает затем уже весь пул. Анализ состояния БД показал что, там ничего не зависает, а просто некий insert готов «выполняться сутками», причем дело происходит в процедуре и инсерт идет во временную таблицу - в общем все концы спрятаны. Остается только найти какие праметры и контекст передаются процедуре (да да процедура использует sys_context) и залезть отладчиком в процедуру. Итак проблема чуть упростилась: есть черный ящик (ЧЯ) с полтергейстом - на вход ему поступают некие данные (в данном случе Http запрос), надо проверить гипотезу, что при некоторых входных данных в ЧЯ «зависает» одно соединение и добится воспроизводимости проблемы. Соответственно решение состоит из двух частей:

  • записывать входные параметры
  • если поток «завис» то сообщать об этом

Отмечу несколько нюансов: т.к. используется http сессия и иногда post запрос то обычный http-access лог тут не помогает, писать всю собранную о http запросе информацию нельзя - слишком большая нагрузка. Решение Создаем класс DiagnosticInfoHolder (singleton основанный на ThredLocal) в него помещается объект DiagnosticInfo уникальный для каждого запроса и содержащий:

  • время запроса
  • параметры http запроса
  • идентификатор авторизованного пользователя (userId)
  • идентификатор потока

В общем все, что может пригодиться при диагностики проблем. Он должен быть доступен глобально по всему приложению, т.к. инициализация DiagnosticInfo размещается в javax.servlet.Filter, а userId становится доступен лишь в недрах приложения.

Создаем класс Watchdog который содержит планировщик и регулярно проверят все DiagnosticInfo. Если запрос выполняется дольше указанного времени то в лог отправляется соответвующее сообщение. Как только запрос отработал также в лог помещается сообщение.

Поиск ошибки свелся к тому, что после зависания мы нашли первый не закончившийся запрос, взяли из записи параметры и воспороизвели ошибку. К слову проблема была в sql процедуре где вставка во временную таблицу просто зациклилась из-за заполняемого пользователями древовидного справочника в котором появились циклы.

Очевидная модернизация: при зависании снимать диагностическую информацию (дамп потоков, состояние блокировок) и отправлять в лог (под логом подразумевается не обязательно текстовый и необязательно файл).

С картинками тут.

Deleted

Класс. Один зависший запрос вешает весь пул соединений с БД интерпрайзно, глобально и надёжно.

anonymous ()

И теперь добавим еще хардкора:

Делаем декортатор коннекшон пулу:

создаем собственный враппер jdbc-connection'у.

DiagnosticsInfo инициализируем в нем. Authentication делаем как threadlocal, что б вытащить его для DiagnosticsInfo.

Для захвата statement params делаем дополнительыне декораторы.

Профит: внедряемость в имеющееся приложение без необходимости модифицировать DAO и возможность профайлить любые JDBC вызовы

anonymous ()
Ответ на: комментарий от anonymous

это была бы следующая стадия, еслиб описанный вариант не помог

только

DiagnosticsInfo инициализируем в нем.

вообще это один из вариантов паттерна Mapped Diagnostic Context, и его лучше инициализировать в самом «верхнем вызове», т.е. в веб приложении в entry point.

Deleted ()
Ответ на: комментарий от Deleted

Возможно. Я на это смотрел лишь со стороны отлавливания лагучих вызовов.

anonymous ()

Стандартная проблема - стандартное решение. Почти как на прошлой работе.

ya-betmen ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.