Рейтинг:2

Повторное развертывание Tomcat и заброшенные (спящие) соединения MySQL

флаг fr

TL;DR: повторите развертывание, перезапустив сервер (проверьте окончательное обновление).

Это старый вопрос, но пока не нашел решения, и на данный момент я не знаю, где искать.

У нас есть три веб-приложения Java (Spring, без загрузки), развернутые на двух серверах: одно приложение находится на вычислительном движке GCP, развернутом на Tomcat9 (порт 80), а два других — на нашем собственном сервере на одном экземпляре Tomcat8.5 ( порт 8080 перенаправлен с порта 80). Обе системы имеют Mysql8 и используют практически одинаковую конфигурацию для подключения к нему: DataSource для основной БД и ConnectionProvider для арендаторов.

Проблема в том, что при повторном развертывании некоторые старые соединения из пула (HikariCP) не уничтожаются, тогда как другие убиваются. Эти соединения, которые остаются, принадлежат нашему поставщику мультитенантных соединений.Главный арендатор так сказать убивает старые соединения как следует. Это, конечно, приводит к тому, что у нас так много подключений, что мы исчерпаем их, выбрасывая SQLExceptions. Я исправил это, увеличив количество подключений, но это не решение.

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

Что я пробовал:

  • Я видел связанные ответы об этом (в основном связанные с PHP), где Соединения Mysql остаются в состоянии сна даже после того, как их работа завершена. Я пробовал также исправления, представленные в этих вопросах, потому что они казался разумным и для моего случая. Такие вещи, как уменьшение в ожидание_тайм-аут и интерактивный_тайм-аут до 30 минут.
  • Наша конфигурация HikariCP отключает соединения через 10 минут, и они maxLifetime от 15 минут. Даже в нерабочее время соединения не закрываются, и они фактически обновляются после этих 30 минут. Этим Я имею в виду, что время, отображаемое запросом SELECT * FROM information_schema.processlist GROUP BY db; доходит до 1799 (даже меньше), а затем возвращается к 0. Почему? Я знаю, что система не используется пользователями в то время, и журналы показывают, что HikariCP знает только о 4 соединениях (тех, которые я настроил) вместо до 20, которые иногда являются «активными».

Мы используем Spring Data JPA, поэтому все управление соединениями обрабатывается Hibernate/JPA. Соединения также должным образом обновляются Хикари, поэтому я не думаю, что соединения остаются открытыми в коде.

Пока что я уверен, что это не проблема с Hikari (под этим я подразумеваю нашу конфигурацию). Это наводит меня на мысль, что с конфигурацией базы данных что-то не так или мы просто неправильно передислоцируем.

Я считаю, что эта проблема исчезнет, ​​если я реконструирую расположение серверов (простите за отсутствие словарного запаса), разместив оба веб-приложения в их собственном экземпляре Tomcat и используя Apache или Nginx для их прокси. Я сделал эту конфигурацию в своей тестовой среде, и я давно хотел это сделать, но трудно оправдать такое изменение в моей позиции (в значительной степени не совсем младший, но бэкэнд-разработчик, который каким-то образом взялся за это дело). Тем не менее, это большое изменение, оно займет у меня пару дней, пока я работаю над другими вещами, и я действительно (правильно) исправляю текущую конфигурацию, а не перестраиваю сервер.

Другие варианты — запланировать перезапуск сервера + БД. Наша система региональная, и наши пока еще немногочисленные пользователи работают в обычное время, поэтому они никогда не заметят ежедневный перезапуск, скажем, в 3 часа ночи. Мне это просто не нравится, и я думаю, что это так же неэффективно, как слепое увеличение max_connections каждый день ИМО.

Существует также возможность перестроить то, как мы обращаемся с нашими многочисленными арендаторами. Мы используем ConnectionProvider, и эти соединения являются «неисправными». Я видел несколько примеров других подходов, использующих DataSource, и я знаю, что DataSource не имеет этой проблемы, потому что «основные» соединения с базой данных обрываются, как и ожидалось, при повторном развертывании. Тем не менее, я все еще считаю, что это проблема конфигурации.

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

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

ОБНОВИТЬ:

Я развернул несколько дополнительных методов в нашем бэкенде, которые, вероятно, должны помочь с дополнительными соединениями. Некоторые методы не были переопределены, и, поскольку мы расширяемся из другого класса, суперметод может не работать. Эти методы нацелены, в частности, на структуру данных, из которой осуществляется доступ к соединениям.

Кроме того, после одного повторного развертывания я увидел, что количество подключений увеличилось с 4 до 8 (ожидалось: 4 из первого развертывания и дополнительные 4 из повторного развертывания), но через несколько часов количество подключений уменьшилось до 6. Я надеялся, что это было конец этого, но на следующий день у нас снова были эти 8 подключений.

Хуже того, сегодня у меня была возможность перезапустить некоторые службы, и я экспериментировал с перезапуском только службы базы данных. Вначале казалось, что количество подключений снизилось до ожидаемых 4 на каждого арендатора, но через некоторое время оно увеличилось до того же значения, которое было до перезапуска. Это говорит мне о том, что соединения удерживаются в заложниках (?) Tomcat, а это означает, что, возможно, в документации есть что-то, касающееся этого поведения. Я не нашел правильных ключевых слов, чтобы найти его, но я делаю ставку на контекст, область или один вентиль.

Если я ничего не найду, я разверну созданный на заказ ConnectionProvider, который я расширил из EntityManagerFactoryBean. В этом я установил останавливаться() метод, вызывающий @PreDestroy доступ к структуре данных с подключениями арендаторов и их ручное закрытие с помощью собственных методов Hikari. Теоретически, это максимум, что я могу сделать из кода, чтобы закрыть эти соединения. Если это не сработает, и я также не могу найти ничего в документации Tomcat, мне нужно будет высказаться и выбрать между запланированными перезапусками или перестроением сервера + «правильными повторными развертываниями» (остановка, обновление, запуск).

ОБНОВЛЕНИЕ 2:

Вчера я вложился, пытаясь вручную закрыть соединения, используя метод, описанный в последнем обновлении, и другой метод, помогающий мне с ServletContextListener. Ни один не работал, и обнаружил, что метод близко() в HikariCPs поставщик соединений не ссылался на соединения, так что да. Я также решил попытаться динамически генерировать ConnectionProviders в bean-компоненте с помощью правильного метода закрытия/уничтожения, но поскольку метод, который я использовал, не предназначен для этого, я частично откажусь от этой идеи.

Далее: изменить с ConnectionProviders к Источники данных. Если это сработает, то мы можем продолжить повторное развертывание, как всегда. Я попробую три метода, которые я придумал (на случай, если соединения вызывают ту же проблему при повторном развертывании): @PreDestroy метод для ручной итерации карты DataSources и закрытия всех соответствующих соединений, динамического создания и регистрации всех Источник данныхs как bean-компоненты (вероятно, «группируя» их с интерфейсом или чем-то еще, поэтому MultiTenantResolver может работать с ним или использовать первый подход, но закрывая соединения в ServletContextListener.

Еще я обнаружил, что соединения поддерживаются на уровне выше контекста веб-приложений. Это ключевая информация, но я, честно говоря, недостаточно понимаю, почему набор соединений из одного приложения не закрывается, а другой набор, а не почему Tomcat не позволяет этим потокам/соединениям умирать по истечении времени ожидания. Источником этой информации является этот вопрос от StackOverflow.

Мне удалось незаметно «отрезать себе кусок сервера» и настроить персональную среду тестирования внутри среды тестирования. Поскольку технически я отвечаю за это и за это, пытаясь исправить то, что сейчас находится в производстве, я думаю, что это оправдано.

я мог бы попробовать спрашивать в SO и HikariCPs Google Group, хотя и с разными целями, чтобы мой вопрос был актуален для обоих сообществ.

ОБНОВЛЕНИЕ 3

Переход с ConnectionProvider на DataSource решил половину проблем и принес новые, более запутанные ошибки:

  • В то время как большинство пулов были правильно инициализированы при 4 соединениях при повторном развертывании, два из этих пулов остались в старом поведении (4 из исходного развертывания + 4 нового развертывания), а один каким-то образом оказался с 12 при повторном развертывании. Это исходные 4, 4 из передислокации и еще 4 случайных дополнительных.
  • Во время тестирования системы на предмет странного поведения я заметил, что каждый раз, когда я меняю арендаторов, создается новый пул. Позже я выяснил, что на самом деле при запуске создавались только два пула, а каждый второй пул создавался только по запросу. На самом деле это было нормально, но у меня все еще был один арендатор с некоторыми случайными подключениями при запуске, которые происходили при использовании этой конкретной базы данных.

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

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

В документации Hikari указано, что для горячего развертывания (и горячего повторного развертывания по расширению) необходимо закрывать соединения, но в нем говорится о источниках данных, а не о ConnectionProvider. На данный момент я даже подумываю отказаться от Хикари в пользу другого решения, но я также чувствую, что это не нужно и является результатом моего разочарования.

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

Обновление 4:

Ну наконец-то я сдался. Я поговорил с теми, с кем мне нужно было поговорить, и фактически получил крайний срок, чтобы закончить другие дела, включая небольшой капитальный ремонт наших серверов. Это было одной из причин, по которой я тоже начал изучать это.В любом случае, учитывая этот крайний срок и учитывая, что я просто не нашел решения, я перестрою структуру серверов: я буду использовать прокси-сервер, чтобы предоставить каждому приложению экземпляр Tomcat в разных защищенных портах. Таким образом, клиентам не нужно ничего менять. Внутри я предоставлю руководителям проектов сценарии развертывания, которые будут обновлять их ветку развертывания, генерировать обновленный WAR, останавливать конкретную службу Tomcat, очищать предыдущие сборки, добавлять новую сборку и снова запускать службу Tomcat. Таким образом, мне не нужно беспокоиться о соединениях, наконец, дать каждому проекту необходимую независимость и автоматизировать развертывание, чтобы избежать как можно большего количества ошибок.

Не буду врать, это немного отстойно, так заканчивается, но мы не всегда побеждаем, верно?

Ответить или комментировать

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