Рассмотрим следующую таблицу «pending_send_confirm» в базе данных, используемую для хранения адресов электронной почты, на которые мне нужно отправить электронную почту (конечный пользователь подтвердит свою подписку через это электронное письмо):
id| address |occupied|
0 |[email protected]|0 |
1 |[email protected]|0 |
2 |[email protected]|0 |
Когда пользователь подписывается (например, на мою рассылку), я хочу показать сообщение о том, что почти сразу все прошло нормально. Я не хочу, чтобы он ждал, пока сервер отправит электронное письмо с подтверждением на его адрес, прежде чем показывать сообщение.
По этой причине я настраиваю задание cron, запускаемое каждую минуту, чтобы управлять электронными письмами, которые необходимо отправить. Согласно таблице, которую я придумал (показана выше), это некий псевдокод, отправляющий электронные письма на каждый адрес, 1 на 1:
//child_script.php
//fetch the entries from the database
while ($entry = $result->fetch_assoc()) {
if ($entry['occupied']) {
/*
* Another instance of this script has 'occupied' this address, which
* means that it is currently trying to send a confirmation email to
* this address. So you know that another instance is working with this
* email address, so skip it.
*/
continue;
}
/*
* Entry is not occupied, occupy it now in order to prevent future instances
* of this script to attempt to send additional confirmation email to this address
* while this instance of the script tries to send the confirmation email to this address.
* occupied=1 means that an attempt to send the confirmation email is under the way
*/
occupyEntry($entry['id']); //sets 'occupied' to 1
if (sendConfirmationEmail($entry['address'])) {
/*
* Email was sent successfully, move the email address from the 'pending_send_confirm' to the
* 'pending_confirmation_from_user' table.
*/
moveToConfirmPendingFromUserTable($entry['id']);
} else {
/*
* Failed to send the email, unoccupy the entry so another instance of the script
* can try again sometime in the future
*/
unoccupyEntry($entry['id']); //sets 'occupied' to 0
}
}
Код без комментариев для удобства чтения:
//child_script.php
while ($entry = $result->fetch_assoc()) {
if ($entry['occupied']) {
continue;
}
occupyEntry($entry['id']);
if (sendConfirmationEmail($entry['address'])) {
moveToConfirmPendingTable($entry['id']);
} else {
unoccupyEntry($entry['id']);
}
}
Является ли это надежным решением для предотвращения отправки дубликатов электронных писем? Я беспокоюсь, что 2 экземпляра скрипта могут обнаружить «одновременно», что $entry['occupied']
равно 0 для определенного идентификатора, и попытаться отправить электронное письмо на этот адрес.
Другим решением было бы использовать flock ( Как я могу убедиться, что только один экземпляр PHP-скрипта работает через Apache? ), чтобы гарантировать, что только 1 экземпляр моего скрипта работает.
Однако я вижу много проблем с реализацией стада. Например, что произойдет, если мой сценарий выйдет из строя до вызова fclose($fp)
? Сможет ли следующий экземпляр моего скрипта продолжить работу или он увидит его как еще один запущенный экземпляр скрипта (т. е. что функция flock
вернет новому экземпляру скрипта)? Другая проблема заключается в том, что мой скрипт отправляет электронные письма одно за другим. Это означает, что если у меня есть 100 электронных писем для отправки, и я отправляю их в течение 3,5 минут, то следующий экземпляр запустится через 4 минуты ПОСЛЕ ЗАПУСКА 1-го экземпляра. Это означает, что если подписчик решит подписаться в момент запуска 1-го экземпляра, ему придется ждать более 4 минут, чтобы получить электронное письмо с подтверждением. Если я позволю сценариям работать параллельно, то электронные письма будут рассылаться намного быстрее. Поэтому я бы предпочел иметь несколько экземпляров одного и того же сценария, отправляющих электронные письма одновременно.
С другой стороны, если мой сценарий, использующий метод «оккупировать», работает нормально, смогу ли я использовать другой сценарий для управления количеством одновременных экземпляров, которые будут запущены? Например, смогу ли я сделать следующее? :
//master_script.php
/*
* Launch many instances of child_script.php.
* If there are 901 to 1000 emails, then start 10 instances etc
*/
launch_n_instances( ceil(number_of_non_occupied_entries()/100.0) );
Как правильно решить эту проблему?