Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Perl: Общие вопросы > работа с mySQL


Автор: Secandr 24.2.2015, 14:57
Добрый день,

у меня есть пара простеньких демонов на perl. Основная их задача - сбор статистики с оборудование и запись данных в БД. Есть несколько САР на perl.

Столкнулся с тем, что демоны и САР периодически дохнут при работе с БД. Превышение интервала ожидания, потеря конекта к БД, рестарт БД, ошибки в запросах,... приводят к смерти софтины.

Перепускать скотинку приходится руками, что доставляет определённые неудобства.
Написать ревайвел слишком просто, хотелось бы решить проблему средствами perl.

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

Может уже кто-то написал такой модуль?

Подскажите пожалуйста.

Автор: Bulat 24.2.2015, 15:24
Цитата(Secandr @  24.2.2015,  14:57 Найти цитируемый пост)
Может уже кто-то написал такой модуль?

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

Автор: Secandr 25.2.2015, 10:38
Ну я думаю так.
Делаем класс, который просто переназначает соответствующие методы класса DBI и делов-то.
Там нужно переписать connect, disconnect, prepare, execute, do, fetchrow_array
В основной же библиотеке просто мнеяем use DBI на use myDBI и создание объекта. Всего 4 строчки.

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

На первом этапе просто подменить класс и посмотреть как оно будет работать.

А дальше уже можно добавить доп. логику. Ввести несколько механизмов поведения.

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

Для важных update и insert ввести проверку на выполнение
Там где проверка затруднена или требует много ресурсов научить при ошибке выходить, закрывая за собой всё что надо. Как вариант, не делать exit, а посылать самому себе сигнал kill, что бы его перехватил соответствующий блок основной программы и вышел как полагается.

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

P.S. Вопрос только в том, не буду ли я изобретать велосипед. И будет ли мой велосипед достойнее тех, что уже сделаны.

Автор: Bulat 25.2.2015, 11:18
Цитата(Secandr @  25.2.2015,  10:38 Найти цитируемый пост)
Вопрос только в том, не буду ли я изобретать велосипед.

если ты собираешься делать все это :
Цитата(Secandr @  25.2.2015,  10:38 Найти цитируемый пост)
Там нужно переписать connect, disconnect, prepare, execute, do, fetchrow_array


то потянет на велик.  smile 

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

Есть нормальный коннект - заливаем данные, нет - пробуем два-три раза, до удачной попытки. После двух-трех неудачных попыток, не насилуем никого и ничего, а умираем с криками и орами в лог!  smile

Добавлено через 53 секунды
Ну можешь и по своей логике идти, только смысл переписывать ряд функций из DBI, которые и так хорошо работают!  smile 

Автор: Secandr 25.2.2015, 11:33
Ну вот когда у тебя несколько десятков тысяч строк и вдруг надо поменять DBI в этом всём на что-то другое начинается веселье.

Откровенно лень переписывать пучу кода. Да и опасно. Код-то не ради кода написан, код управляет оборудованием, а отказ в работе оборудования очень неприятен для предприятия...

Проще написать скрипт, который заменит DBI на myDBI и тут же наступит счастье smile

Ну а myDBI будет делать так

Код

sub connect(){
 $class=shift @_;
 eval{
  $class->dbi->connect(@_);
 }
 # повторить попытку $class->N раз.
}

Автор: Bulat 25.2.2015, 13:21
Если в огромном коде, глобальный хендл к бд везде вызывается напрямую, без инкапсуляций или каких-то иных функций-оберток, то myDBI - возможно и самое оптимальное решение!  smile 

Автор: Secandr 25.2.2015, 13:47
Ну там подход немного другой. БД обёрнута и работа большей части библиотеки идёт не с БД напрямую а с сущностями более конкретными, которые описывают рабочее тело. И 80% кода, работающего с БД, лежит в отдельном классе большой библиотеки.

Но есть и печаль. Часть нетипичной работы с БД ведётся напрямую из софта, вызывающего библиотеку, причём не через библиотеку, а напрямую через DBI.

В целом же, проще всё же вынести функционал работы с БД в отдельный класс и уже с ним работать и из основной библиотеки и из софта.

Попутно перетащив часть рутинных операций в этот класс.

P.S. С каждым годом переписывать код становится всё тяжелее smile

Автор: tzirechnoy 25.2.2015, 15:28
Хотите, чтобы иногда ->fetchall_arrayref вызывался по три раза? Ну, сделайте обёртку вида fetchall_arrayref_hard(dbh, ..., 3).

А вообще -- задача правильной обработки ошыбок и корректного преобразования мира при их наличии -- это именно прикладная задача. Она каждый раз будет решаться на основе вашэго прикладного кода, потому внутри DBD этому дажэ помочь особенно нечему.

Ну и, конечно -- сделайте, наконец, так, чтобы mysql не дох. Я дажэ не предлагаю перейти на postgres (хотя это, конечно, стоило бы сделать, но не по этой причине) -- но, знаете, почему-то у сотен тысяч человек mysql работает и не дохнет.

Автор: Сумасшедший 26.2.2015, 08:17
Приношу извинения что не про перл, но почему бы в данном случае не воспользоваться mysql-proxy?

Автор: Secandr 26.2.2015, 10:53
tzirechnoy, если руки дойдут переписать класс, я его потом покажу. Там задача немного более широкая. Но в целом вы правы, не имея понятия о бизнеслогике, нет возможности как-то повысить надёжность.

По поводу "что бы mySQL не дох" могу сказать следующее. Проект управляет сетью размером в 3 города, не считая мелких узлов по области и в столице. Есть несколько серверных ферм. Есть исторически сложившаяся инфраструктура: системы учёта сети, система электронного документооборота, системы мониторинга, технического аудита,... Каждая из систем работает со своей БД. Это и mySQL и postgresql и mssql и тот же битрикс на файликах.

Мой софт работает с этим со всем, собирает данные, анализирует это всё и по заданному шаблону применяет настройки к железу.

Задача - добиться стабильной и предсказуемой работы софта в аварийные периоды. С одной стороны - обеспечить адекватность при потере большей части инфраструктуры. В этом случае лучше умереть и ничего не делать. С другой стороны обеспечить надёжность при мелких авариях. Софт должен подождать спада пиковой нагрузки на сервере БД или спокойно перенести его перезагрузку. Иначе будет большой лаг от желания изменить настройки оборудования до их применения.

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

Автор: Pfailed 28.2.2015, 09:08
https://metacpan.org/pod/DBD::mysql#mysql_auto_reconnect
Ну а ошибки обрабатывать естественно надо. Я обычно устанавливаю RaiseError =>1, PrintError => 0 и ловлю ошибки через eval.

Автор: Secandr 4.3.2015, 09:53
Натыкался в документации на такое, попробую на днях.

Автор: Secandr 6.3.2015, 15:39
Не успел ничего путного сделать за неделю.
Добавил 
Код

$name{'db'}= DBI->connect("DBI:----------", "------", "-------"
        , {
            mysql_auto_reconnect => 1,
        mysql_init_command    => q{SET CHARACTER SET 'utf8';SET NAMES 'utf8';}
          }        
        ) or die "could not connect to HD";

Автор: Secandr 10.3.2015, 09:27
Не помогло.
Теперь вылетает без ошибок :(

Автор: noize 10.3.2015, 23:01
простенький пример из жизни:

Код

my $DBH;

sub dbh {
    unless ( $DBH || $DBH->ping ) {
        $DBH = DBI->connect();
        croak "Can't connect to database: " . $DBH->err if $DBH->err;
    }

    return $DBH;
}

print dbh->selectrow_array( qq/ SELECT NOW() / );


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

Автор: Secandr 11.3.2015, 10:51
noize, пока всё проще. Я забыл в продакшен выложить скрипты с изменениями smile

Автор: noize 11.3.2015, 20:56
я предпочитаю с подобными делами(база, кеш) всегда работать через методы. Так у вас всегда имеется единственная точка входа и если надо поменять логику работы с базой, то достаточно изменить только один метод и всё заработает.

Автор: Secandr 12.3.2015, 10:00
noize я начал этот проект 7 лет назад со скриптов на bash, лет 6 назад перешёл на перл, лет 5 назад переписал всё в функциональном стиле. 3 года назад перешёл на ООП. Полтора года назад провёл рефакторинг бибилиотеки, поскольку не совсем понял парадигму ООП с первого раза.

Если бы я был программистом, было бы проще smile

Автор: Pfailed 13.3.2015, 08:44
Цитата(noize @ 10.3.2015,  23:01)
простенький пример из жизни:

Код

my $DBH;

sub dbh {
    unless ( $DBH || $DBH->ping ) {
        $DBH = DBI->connect();
        croak "Can't connect to database: " . $DBH->err if $DBH->err;
    }

    return $DBH;
}

print dbh->selectrow_array( qq/ SELECT NOW() / );


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

А не
Код

unless ($DBH && $DBH->ping)

?

Автор: Bulat 13.3.2015, 09:04
Цитата(Secandr @  12.3.2015,  10:00 Найти цитируемый пост)
Полтора года назад провёл рефакторинг бибилиотеки, поскольку не совсем понял парадигму ООП с первого раза.

Если бы я был программистом, было бы проще

А где ты здесь увидел программистов??  smile 

Лично я сколько не пытался писать проекты в ООП стиле - честно говоря, ничем хорошим это так и не заканчивалось!  smile 

P.S. хотя я кстати-таки дипломированный программист!  smile 

Автор: Secandr 13.3.2015, 14:16
Bulat, я дипломированный металлург smile
И ООП мне был не нужен нафик. Пока проект не вырос до размеров полноценной библиотеки. Без наследования и разбивки по классам код стал совершенно нечитаем. Пришлось перейти на ООП. И оказалось, что это чрезвычайно удобно.

Вот только отсутствие опыта иногда сильно затрудняет разработку.



Pfailed, noize, спасибо, будут допиливать SQL.

Кстати, трое суток, полёт нормальный, похоже просто по таймауту дох коннект.

Автор: Secandr 13.3.2015, 16:52
Сделал так:

Код

package noDB;
use DBI;
use base qw(noNull);
 
sub new { # конструктор
 my $class = shift; 
 $DBname =  shift;
 my %name;
 $name{'DBname'}=$DBname;
 $class->debug("new device $IP");
 return bless \%name , $class; 
 }

sub connect {
 my $class = shift;
 $class->debug("connect to $class->{DBname}");

 my $dsn; my $usr; my $pwd; 
 if ($class->{DBname} eq 'helpdesk'){
#
#
#
  }elsif($class->{DBname} eq 'TVradius'){
#
#
#
  }elsif($class->{DBname} eq 'ONOF'){
#
#
#
  }else{
    die("could not connect to DB  $class->{DBname} no such name")
  }

    $class->{DBI}=DBI->connect($dsn, $usr, $pwd,  
        , {
           RaiseError        => 0,
              PrintError        => 1,
            mysql_auto_reconnect => 1,
              mysql_init_command    => q{SET CHARACTER SET 'utf8'}
          }        
        ) or die "could not connect to helpdesk";

  
 return $class;
}

sub prepare {
 my $class = shift;
 $class->debug("DBprepare");
 my $sql=shift;
 if (! $class->{DBI}->ping()){
   $class->error("DB $class->{DBname} Faild");
  sleep (60);
  $class->connect;
 }
 my $res = $class->{DBI}->prepare($sql);
 if ($res->err){
    $class->error("\ncould not prepare \n$sql\nfor DB $class->{DBname}. Got error:".$res->err);
    die "SQL prepare die";
 }
 $res->execute;
 
 if ($res->err){
    $class->error("\ncould not prepare \n$sql\nfor DB $class->{DBname}. Got error:".$res->err);
    die "SQL execute die";
 }
 return $res;
}


return 1


Заменил в основном коде DBI на noDB. Убрал do и execute.

В продакшен пока выкладывать не буду.

Автор: noize 13.3.2015, 21:25
Цитата(Pfailed @  13.3.2015,  09:44 Найти цитируемый пост)
А не
Выделить всёБез подсветки
1:
    
unless ($DBH && $DBH->ping)

? 


Да, там нужно условие '&&' вместо '||', пардон. Написал в спешке)

Добавлено через 8 минут и 49 секунд
Secandr, советую используйте прагмы 'strict' и 'warnings' везде, где это возможно.
Это позвлит кардинально сократить количество возможных ошибок.

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

Автор: Secandr 16.3.2015, 09:31
noize, а разве use не наследуются через "use base qw(noNull);" ?

У нас с программистами всё гораздо проще. Они на perl не пишут. Я по своему софту делаю ТЗ, сдаю им на разработку, свой софт по окончанию разработки уьиваю.
А сам пишу софт только в том случае, когда быстрее написать софт, чем ТЗ.

Автор: Pfailed 16.3.2015, 16:35
Secandr, strict и warnings не наследуются, они включают своё действие только в области где были заuseны

Добавлено через 2 минуты и 55 секунд
Либо в noNull надо создать метод import и в нём сделать вот так https://metacpan.org/source/SRI/Mojolicious-6.03/lib/Mojo/Base.pm#L42
Тогда и в noDB будут действовать strict и warnings.

Автор: Secandr 16.3.2015, 16:47
Pfailed, Спасибо. А я-то думал, что наследуются...

Автор: Bulat 17.3.2015, 08:31
Цитата(Secandr @  16.3.2015,  09:31 Найти цитируемый пост)
У нас с программистами всё гораздо проще. Они на perl не пишут.

Скучные у вас какие-то программисты...  smile 

Автор: Secandr 17.3.2015, 11:34
Bulat, инженеры у нас обычно знают 1-2 языка программирования и пишут на чём умеют. Я в том числе пишу на perl, просто потому что его выучил ещё 10 лет назад.

А программисты используют тот язык который будет удобнее для проекта. И обычно это что-то с хорошим ООП: Java, фреймворки php, C#. Да и проекты программистов несколько отличаются от поделок инженеров масштабами.

Ну и есть замечательное разделение софта. Программисты не лезут в недософт инженеров, а инженеры не лезут в софт программистов. Если недософт инженеры не успевают поддерживать, его переписывают программисты.

Я только в страшном сне могу представить систему управления предприятием написанную на perl или биллинг, к примеру smile

Так что да. У нас скучные программисты smile


По теме топика: неделя прошла - полёт нормальный.
Видимо mySQL скидывал демона по timeout`у. Демон засыпает на несколько секунд, если нет работы, что бы не отъедать load на стареньком сервере.

Автор: tzirechnoy 17.3.2015, 14:53
Цитата
 или биллинг, к примеру 


Я вполне видел биллинг на perl. Ну, большынство поделок на C# и Delphi -- хужэ.

Автор: Bulat 17.3.2015, 16:11
Secandr, это уже холивар и оффтоп, поэтому лучше не оффтопить! Скажу в завершении - если вы чего-то не умеете, то это не повод говорить - кошмар, в страшном сне, а хороший повод расти дальше!  smile 

Цитата(tzirechnoy @  17.3.2015,  14:53 Найти цитируемый пост)
Я вполне видел биллинг на perl. 

А я не то, что видел, но еще и дописывал новые модули для новых сервисов!  smile Дабы не нарушать общую архитектуру!  smile 

Автор: Secandr 17.3.2015, 16:45
Цитата(noize @  13.3.2015,  22:25 Найти цитируемый пост)
Secandr, советую используйте прагмы 'strict' и 'warnings' везде, где это возможно.


Пол дня потратил на то что бы скрипт запустился после 'strict'. Была пара мест забавных, где стояли просто символы, видимо нечаянно введённые при скролле. Было пару грубых ошибок в бизнеслогике. Поражаюсь как оно вообще работало smile

Правда теперь на одно моё сообщение в логах десяток от 'warnings' :(


Bulat, я сам snmpd переписывал на перл. Работает гораздо шустрее родного, порядка на 2. И биллинг видел на perl, самолично интегрировал такой.

Тут вопрос не холивара, а практичности.  Если у вас 100 000 одновременных сессий и довольно сложная система, то можно и на перле написать, но возникает проблема вот в чём. Сколько человекочасов будет потрачено на разработку? Сколько денег? Сколько на дебаг? Сколько будет стоить час работы гуру в перле на поддержку под такой проект?

И тут уже выясняется, что быстрее дешевле и надёжнее купить коробку с биллингом или взять open source решение и допилить силами средненького админа или интегратора.

Автор: tzirechnoy 18.3.2015, 14:03
Цитата
. Сколько человекочасов будет потрачено на разработку? Сколько денег? Сколько на дебаг?


Думаю, меньшэ чем если писать на питоне или php.

Цитата
Сколько будет стоить час работы гуру в перле на поддержку под такой проект?


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

Цитата
купить коробку с биллингом 


Ну, начинается. Мы -- про вопрос на чём можно писать, а не можно ли где-то купить.

Автор: Secandr 18.3.2015, 14:48
tzirechnoy, можно и на асемблере писать, вот это как раз вопрос холивара.
А в жизни всё прозаичнее - ресурсы ограничены и чаще всего выгодно вообще не писать.

Автор: Secandr 24.3.2015, 16:58
Вот, кстати, ещё один интересный момент. Уже в другом софте пригодилось сделать вот так:

Код

my %WARNING;
my %buffer;
my @childs;              #массив потомков
$SIG{__WARN__} = sub {
                        my $msg = shift;
                        chomp($msg);
                        $WARNING{$msg}=1;
                        warn "WARNING: ", $msg,"\n";
                     };
$SIG{__DIE__} =  sub {
                        my $msg = shift;
                        chomp($msg);
                        print "\n\nALL WARNINGS:\n".join("\n", sort keys %WARNING)."\ngot SIG{__DIE__}\n"; 
                        IPC::Shareable->clean_up_all;
                        if (open (SM, "|/usr/sbin/sendmail -t")){
                         print SM "To: ------------\n";
                         print SM "From: ----------------\n";
                         print SM "Subject:!!!!! [ $$ ] got sig __DIE__ at ".localtime()."\n\n";
                         print SM $msg; 
                         print SM "\nALL WARNINGS:\n".join("\n", sort keys %WARNING)."\ngot SIG{__DIE__}\n";
                         print SM "\nbuffer:".Dumper(\%buffer);
                         print SM "\nchilds:".Dumper(\@childs);
                         print SM "\nmore information look on caladan in file /var/log/openFILEbySCRIPT.log\n";
                         close SM;
                        }
                        print $msg;
                        exit;
                      };
$SIG{INT} = \&closePID; # -3
$SIG{TRAP} = \&closePID; # -6
$SIG{TERM} = \&closePID; # -16
$SIG{PIPE} = \&closePID; # 
sub closePID {  
                        my $sig = shift;
                        print "\n\nALL WARNINGS:\n".join("\n", sort keys %WARNING);
                        print "\n\n\nGET SIG $sig\nExit.\n\n\n";
                        IPC::Shareable->clean_up_all;
                        if (open (SM, "|/usr/sbin/sendmail -t")){
                         print SM "To: ----------\n";
                         print SM "From: --------------\n";
                         print SM "Subject:!!!!! [ $$ ] got sig $sig at ".localtime()."\n\n";
                         print SM "\nALL WARNINGS:\n".join("\n", sort keys %WARNING)."\ngot SIG{__DIE__}\n"; 
                         print SM "\nbuffer:".Dumper(\%buffer);
                         print SM "\nchilds:".Dumper(\@childs);
                         print SM "\nmore information look on caladan in file /var/log/openFILEbySCRIPT.log\n";
                         close SM;
                        }
                        exit;
             };


Перехватил и сохранил warning;
Перехватил die и отправил на почту все сообщения warning, die и дамп важных переменных.
Перехватил все сигналы, какие знаю, и так же выслал отладочную информацию на почту.

Особенно интересно смотреть почему сдохли дети порождённые в eval.
eval избавил меня от подыхания всего софта, но перехватить причину гибели потомка я не смог. А парсить многотонные логи ежедневно очень тоскливо...

Нашёл ещё несколько забавных ошибок таким способом...

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

Вот.

При следующем рестарте перенесу код в демона, посмотрим чего, ещё замечательного я накодил smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)