.

Привет, все читателя моего блога. Сегодня я хочу затронуть такую не маленькую тему «Проверка доступности сервера Mysql». В этой статье я хочу сделать небольшой обзор на разные подходы, которые я использовал уже в своих проектах. Они так сказать проверены в бою.

Предисловие.

С начала я расскажу, зачем нужно проверять доступность сервера. Несколько лет назад я только начинал писать клиент-серверное приложение. У меня был прямой доступ к серверу, на котором стоял Mysql, в котором я начинал проектирование базы данных. Наступил момент, когда пришло время поставить первую beta-версию в одном из удаленных офисов нашей компании.
Для справки, в том офисе своего админа не было, как и в принципе хотя бы одного здравомыслящего человека в сфере IT. Офис располагался в старом здании, причем занимал два кабинета не плохо удаленные друг от друга (чтобы разделять поток пришедших людей на тех, кто за полисом и кто только заказать). Сетка была в ужасном состоянии, да еще и провайдер предоставлял интернет посредством ADSL- модема, причем на два кабинета.
Дальше все было по принципу «ПОСМОТРЕЛ, УЖАСНУЛСЯ, НАЧАЛ РАБОТАТЬ…». Сначала я просто делал проверку готовности полиса, т.е. чтобы у них был прямой доступ к моей актуальной базе данных. Настроил VPN-соединение до сервера, поставил программу и завел учетные записи для нее. Сел рядом с оператором и стал ждать. Причем ждать, долго не пришлось.

Вследствие того, что соединение было, не лучшего качества, а программа не умела переподключать (reconnect) соединение при потере связи выглядело это ужасно. Оператор открывал форму выдачи полиса, в этот момент на соединении терялось несколько пакетов - все соединении потеряно, но «Connect» для соединения с сервером Mysql еще не знает об этом. Оператор вносит данные, даже не подозревая, что соединение вылетело. После внесения жмет на «Сохранить», программа посылает запрос в сторону сервера. В зависимости от компонента используемого для соединения либо программа просто повиснет так и не получив ответа от сервера, либо просто выведет исключение типа «Lost connect….». Но даже если после этого соединение восстановится, программу приходилось перезапускать, потому как нужно было в обязательном порядке передернуть флаг соединения в «Connect».

А так как я уже сказал сетка – д….., провайдер – г….. пришлось судорожно разрабатывать функцию проверки соединения с сервером.

Варианты проверки доступности сервера Mysql.

Задачка была не тривиальная. Идея была следующая: при потере соединения с сервером программа блокировалась до восстановления соединения. В первую очередь я начал думать в сторону блокировки программы, ведь в ней много форм и меню, кнопок, полей ввода… Как все это разом заблокировать при потери соединения – и тут мне пришла идея.

ошибка подключения

 

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

Первый вариант. Два Connecta.

(не очень хороший, не рекомендую). Я расскажу это вариант больше для ознакомления. Первой идей, которая пришла мне в голову завести еще один независимый «Connect» (назовем его «ConnectTest»), подключённый как рабочий, но не имеющий не одного Query (компонент через который идет выполнение запроса Sql). Тогда я писал на Delphi 7 и для соединения использовал ZeosDbo (7.0.3-stable). Суть метода в следующем, просто при запуске программы запускаем таймер (у меня это 3 сек. (3000 млс.), таймеры работают в миллисекундах), который через указанный промежуток выполняет действие. Он просто продергивал флаг подключения у «ConnectTest», причем код выполнялся в обертке «try…except». Если возникала ошибка при обработке переподключения (reconnect) у «ConnectTest», то выводилась форма блокировки и запускался второй таймер. Второй таймер с промежутком 1 сек. Проверял подключение (передергивал флаг подключения у «ConnectTest» в обертке «try…except»), повторял операцию пока она не проходила без исключений – это означало соединение восстановлено. Как только соединение восстанавливалось, второй таймер вырубался, включая первый и все начиналось сначала.
Весь алгоритм действий данной системы проверки соединения, можно представить в виде схемы.

схема два Connecta

Плюсы:

- Работает метод точно, без сучка и задоринки.
- Можно было сделать и без независимого «ConnectTest», но передергивать рабочий «Connect» не к чему хорошем не приведет. В связи с этим, ввод независимого «ConnectTest», увеличивает стабильность работы все системы.

Минусы:

- Главный минус данного подхода в то, что каждый клиент открывает по два соединения с сервером. То есть например у вас 30 подключённых клиентов, а это значит 60 соединений с сервером!!! Лишние 30 соединений, это тоже нагрузка на сервер.

Второй вариант. Использовать в ZConnection функцию PingServer.

(вариант не плохой, правда ограничен применением ZeosDbo). Данный метод выполним только для ZeosDbo (7.0.3-stable). Я вроде читал, в версии 5 UniDac, тоже добавлена функция ping. Но я не проверял это, потому как я использую версию ниже.
Вернемся к ZeosDbo и Delphi 7. Схема остается, примерно прежней с небольшими исправлениями. Будем использовать один Timer, если функция Ping вернет (False) – означает, что сервер Mysql не доступен.
Запускаем проверку на восстановление соединения с сервером и выводим, как описано выше в первом варианте.
Привожу рабочий код, как это реализовано в моем реальном проекте:

Var
Flag:Integer;
……….
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if DataModule.ZConnect.PingServer
  then
    begin
      if  flag= 1 then
        begin
          FormErorr.close;
        end;
    end
  else
    begin
      if  flag= 0 then
        begin
          flag:=1;
          FormErorr.ShowModal;
        end;
      try
        DataModule.ZConnect.Connected:=true;
      except
      end;
   end;
end;

Функция PingServer():Boolean; - возвращает ложь если соединение потерянно. Переменная Flag используется, чтобы выполнять вызов строки «FormErorr.ShowModal;», производился только один раз. В остальном данный вариант мало отличается, от первого. Правда здесь период я устанавливал 9 сек.

Плюсы:

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

Минусы:

- Я не нашел версию ZeosDbo, адаптированную по Delphi XE. А данный метод необходимо использование компонента ZeosDbo.

Третий вариант. Использование Reconnect в компоненте UniDac.

(не рекомендую, но может кому-то пригодиться).
Пришло время рассказать про такой компонент как UniDac. Компонент крутой и мощный у него даже есть собственная система (Reconnect), которая позволяет в автомате передергивать подключение, при потери соединения с сервером.
Для того что бы задействовать автоматическое переподключение к серверу (reconnect), нужно в компоненте Connection, в разделе свойств «Options» убирать все галочки и оставить только на последнем пункте LocalFailover.
Теперь во вкладке События «Evest», находим событие OnConnectionLost. Cобытие вызывается, при потере соединения с сервером. В него пишем следующий код:

uses  MemData; //Прописываем руками
…..

procedure TForm1.UniConnection1ConnectionLost(Sender: TObject;
  Component: TComponent; ConnLostCause: TConnLostCause;
  var RetryMode: TRetryMode);
begin
  ///rmReconnect- восстановления соединения
/// rmReconnectExecute - восстановления соединения
//и выполнение команды на которой произошел обрыв соединения
  RetryMode := rmReconnect; // Выбираем режим
end;

У данного метода, есть только один большой минус. Например, вы работаете на модеме и у вас соединение потерянно не на пару секунд, а небольшое (очень короткое) время. То выше описанный код, просто превращается в бесконечный цикл, вследствие чего программа просто зависает. И может дойти до того, что система Windows сама предложит убить этот процесс.
Выше описанное положение дел я выяснил, проводя экспоненты с данным методом. Исходя из положения дел, я стал искать, как доработать этот код.
Пришел к вот такому варианту.

Var
RetryCount:Integer;
…………………
procedure TMainForm.MyConnection1ConnectionLost(Sender: TObject;
  Component: TComponent; ConnLostCause: TConnLostCause;
  var RetryMode: TRetryMode);
begin
  if RetryCount < 2 then begin
    Inc(RetryCount);
    RetryMode := rmReconnectExecute;
  end
  else begin
    RetryCount := 0;
    RetryMode := rmRaise;
  //здесь я вставлял код запуска таймера
  end;
end;

В данном примере, программа пытается восстановить соединение 2 раза. После чего, можно выводить форму с сообщением об ошибке подключения и проверять доступность сервера Mysql, как показано в первых двух примерах, простым передергиваем флага подключение в обертке «try…expect».

Плюсы:

- Код позволяет восстанавливать связь именно сразу после разрыва, а не постоянно пингуя (ping) соединение с сервером и проверяя его доступность. Это может сэкономить трафик.

Минусы:

- Медленная реакция. Ставил не один эксперимент, код медленно реагирует на разрыв соединения.
- Возможно из-за того что связь с сервером осуществляется через туннель VPN, код почему-то не всегда отрабатывал. То есть я вставлял модем в ноутбук, устанавливал подключение с сервером, проверял связь с сервером MySQL делая запрос. После просто вытаскивал модем, VPN падал через несколько секунд, а вот программа иногда вообще не понимала, что связь с сервером потеряна.

Четвертый вариант. Пингуем (Ping) сервер.

(способ самый клевый, подойдет для любого сервера, очень отзывчивый и точный)
Я много искал информации по запросы «ping средствами delphi», «ping сервера mysql» и т.д. Но максимум, что я находил это копипаст одной и тоже статьи о том использовать Api-функции Windows для отправки пакетов ICMP, реализация в Delphi. Я пробовал этот метод. Но суть в том, что на выходе вы получаете, тип «record», с данными ответа на ping. А мне нужна была функция, которая бы просто возвращала, прошел ping или нет.
Искал, я искал....Случайно наткнулся на help по пакету Synapse, и там обнаружил функцию PingHost (IpAdress:String):Integer;. Функция PingHost проверяет соединение с указанным ip-адресом посылая на него пакеты ICMP (если я правильно понял исходные коды компонента, то размер пакета составляет 8 байт). Возвращает она время ответа сервера, и если ответа нет вернет -1.
Исходя из этого я просто адаптировал код, приведённый во втором варианте. В итоге получилось вот что:

Uses pingsend; //Библиотека из пакета Synapse

Var
Flag:Integer;

procedure TMainForm.Timer1Timer(Sender: TObject);
begin
  if PingHost('192.168.0.1')<>-1
  then
    begin
      if  flag= 1 then
        begin
          form2.close;
        end;
    end
  else
    begin
      if  flag= 0 then
        begin
          flag:=1;
          form2.ShowModal;
        end;
      try
        Connection.Connected:=true;
      except
      end;
   end
end;

Установить пакет Synapse, очень просто. Для новичком!! Просто скачиваете с моего сайта Synapse. Распакуйте архив в любую папку. В разделе «Library path», добавляете путь к этой папке. Все теперь просто пропишите в «uses pingsend». И приведенный код будет работать.

Плюсы:

- Можно проверять соединение с любым сервером, не только mysql.
- Быстрая реакция кода. Я запускал проверку, в таймере с периодом 3 сек. Зависаний программы я не заметил, просто потому что после того как соединения я разорвал она моментально заблокировалась. Потом я следил за восстановление соединения, через командную строку Windows. B как только командная строка показала, что первый же пакет долетел до цели, программа разлочилась. Вот это меня приятно удивило.

Минусы:

- Нельзя предать, буквенное обозначение адреса сервера, обязательно прямой ip-адрес.

Заключение и выводы.

Я написал этот обзор о моем можно сказать 2-летнем опыте по теме проверки соединения с сервером mysql, автоматическим преподлючении (reconnect) к серверу. Повторюсь если вы пишете на Delphi 7 то вам подойдет второй вариант, если уже на более поздних версия по мне так лучше использовать четвертый вариант. Я бы хотел обратить ваше внимание не пытайтесь делать, как советую на форумах выполнять запрос "SELECT 1", и отлавливать исключение в обертке "try..except". Я уже писал что запрос весит пока, не закончится таймаут. К слову в опциях Connection компонента UniDac, можно выставить время таймаута. Например, таймаут истек ответа от сервера не пришло, НО это не значит что сервер не доступен. Возможна ситуация когда из-за низкой скорости соединения, он просто не успеет ответить. Да и нагружать опять же его выполнением запроса, каждые несколько секунд, со всех клиентов...Бред...Не делайте так.
На этой ноте, я заканчиваю свой обзор средств проверки доступности сервера Mysql. Жду с нетерпением ваших комментариев… С уважением, ваш Shinobi.

2 комментария


  1. В статье не раскрыта тема о том, что делать, если у нас есть множество датасетов, которые при обрыве связи закрываются. При этом обойти их все в цикле и заново включить тоже слабый вариант, потому что мы не знаем, какие был включены, а какие — нет.
    Так же — обрыв связи может произойти внутри транзакции. Транзакция на сервере обрывается и откатывается, приложение тоже должно прервать операцию, функция, работающая с базой должна получить эксепшен, а потом уже — хорошо бы переконнкетиться.

    Ответить
    • admin_shinobi

      Ценные замечания, помойму я даже натыкался на где-то что на MS SQL если транзакция не выполнилась можно сообщить об этом клиенту. Может поже напишу что-нибудь подобное.

      Ответить

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *