Контроль над частотой отправки почты
Контроль над частотой отправки почты
Самый простой способ избежать лишней почты - добавить в программу меры предосторожности, чтобы устанавливать задержку между сообщениями. Если сценарий запущен постоянно, то очень просто запомнить время отправки последнего сообщения:
$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), которая позволяла выяснить, когда было отправлено последнее сообщение и как вычислялась задержка. Позже, при необходимости изменить программу, такое деление позволит изменить способ хранения состояния. Например, если переписать программу так, чтобы она выполнялась периодически, а не постоянно, то замыкание совсем нетрудно заменить обычной подпрограммой, сохраняющей нужные данные в текстовом файле и потом считывающей их оттуда.