Perl для системного администрирования

       

Контроль над частотой отправки почты



Контроль над частотой отправки почты

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

$last_sent = time;

Если программа запускается один раз в N минут или часов через сгоп в Unix или механизмы планирования задач NT, эту информацию можно переписать в файл, состоящий из одной строки, и считывать его при следующем запуске программы. В подобном случае обязательно обратите внимание на меры предосторожности, перечисленные в главе 1 «Введение».

В зависимости от ситуации можно поэкспериментировать с временем задержки. В этом примере показана экспоненциальная задержка (exponential backoff):

$max = 24*60*60; и максимальная задержка в секундах (1 день)

Sunit = 60;

увеличиваем задержку относительно этого значения (1минута)

# интервал времени, прошедший с момента отправки предыдущего

# сообщения и последняя степень 2, которая использовалась для

# расчета интервала задержки. Созданная нами подпрограмма

# возвращает ссылку на анонимный массив с этой информацией

sub time_closure {

my($stored_sent,$stored_power)=(0,-1); return sub {



(($stored_sent,$stored_power) = @_) if @_; [$stored_sent,$stored_power]; > };

$last_data=&time_closure; # создаем замыкание

ft возвращаем значение "истина" при первом вызове и затем после

# задержки

sub expbackoff {

my($last_sent,$last_power) = @{&$last_data};

# возвращаем true, если это первое наше обращение или если

# текущая задержка истекла с тех пор, как мы спрашивали

последний раз. Если мы возвращаем значение true, мы

запоминаем время последнего утвердительного ответа и

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

if (!$last_sent or ($last_sent +

(($unit -.$last_power >= $max) 9

$max : $unit * 2**$last_power) <= time())){

&$last_data(time().++$last„power); return 1;

}

else {

return 0; } >

Подпрограмма expbackoffQ возвращает значение true (1), если нужно отправить сообщение, и false (0), если нет. При первом вызове она возвращает true, а затем быстро увеличивает время задержки до тех пор, пока значение t rue не станет появляться лишь раз в день.

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

Подпрограмма &time_closure() возвращает ссылку на анонимную подпрограмму, по существу, на небольшой отрывок кода без имени. Позже данная ссылка будет вызывать этот код, используя стандартный синтаксис символических ссылок: &$last_data. Код из анонимной подпрограммы возвращает ссылку на массив, поэтому и используется такая масса знаков пунктуации, чтобы получить доступ к возвращаемым данным:

my($last_sent,$last_power) = @{&$last_data};

Вот и вся тайна, которая скрывается за замыканиями: поскольку ссылка создается в том же блоке, что и переменные $stored_seri: и $sto-red_power (посредством my()), то они схватываются в уникальном контексте. Переменные $stored_sent и $stored_power можно прочитать и изменить только при выполнении кода из этой ссылки. Кроме того, они сохраняют свои значения между вызовами. Например:

создаем замыкание $last_data=&time._closure:

вызываем подпрограмму, устанавливающую значения переменных

&$last_data(1,1);

и пытаемся изменить их за пределами подпрел раммы

$stored__sent - $stored_power = 2:

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

print "@{&$last_data}\n":

Результатом выполнения этого кода будет "1 1", хотя и создается впечатление, что в третьей строке были изменены значения переменных $stored_sent и $stored_power. Да, значения глобальных переменных с теми же именами были изменены, но невозможно затронуть копии, защищенные замыканиями.

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

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

$тах = 60*60»24;

максимальная задержка в секундах (1 день)

$min = 60*5; tt минимальная задержка в секундах (5 минут)

$unit = 60; tt уменьшаем задержку относительно этого значения (1 минута)

$start_power = int log($max/$unit)/log(2): # ищем ближайшую степень двойки

sub time_closure {

my($last_sent,$last_power)=(0,$start_power+l); return sub {

(($last_sent, $last_p<wer) = @_) if ё>_; n keep exponent positive

$last_power = ($last_power > 0) 9

$last:_power : 0; [Siast^sent,$last_power]; } };

$last_data=&t ime_clusiire;

n создаем замыкание

возвращаем ггче при первом вызове и затем после роста

it экспоненты sub exprampup {

my($last_sent,$last_power) = @{&$last_data}.

возвращаем true, если это первое обращение или если

текущая задержка истекла с момента последнего обиащен/я.

Если сообщение отправляется, то мш запоминаем время

последнего ответа и увеличиваем

$min : $unit * 2**$last_power) <= time())){

&$last_data(time(),++$last_power1): return 1;

}

else

{

return 0; } }

В обоих примерах вызывалась дополнительная подпрограмма (&$last_data), которая позволяла выяснить, когда было отправлено последнее сообщение и как вычислялась задержка. Позже, при необходимости изменить программу, такое деление позволит изменить способ хранения состояния. Например, если переписать программу так, чтобы она выполнялась периодически, а не постоянно, то замыкание совсем нетрудно заменить обычной подпрограммой, сохраняющей нужные данные в текстовом файле и потом считывающей их оттуда.



Содержание раздела