首页 文章

通过cron作业发送电子邮件的实现

提问于
浏览
0

考虑数据库中的以下'pending_send_confirm'表,用于存储我需要向其发送电子邮件的电子邮件地址(最终用户将通过该电子邮件确认其订阅):

id| address |occupied|
0 |em1@s.com|0       |
1 |em2@s.com|0       |
2 |em3@s.com|0       |

当用户订阅(例如,我的时事通讯)时,我想要显示一条消息,说明一切都很顺利 . 我不希望他在显示消息之前等待服务器将确认电子邮件发送到他的地址 .

这就是我设置每分钟运行一次cron作业以管理必须发送的电子邮件的原因 . 根据我提出的表格(如上所示),这是一些伪代码向每个地址发送电子邮件,逐个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个实例可能会在"same time"找到特定ID的 $entry['occupied'] 为0并尝试向该地址发送电子邮件 .

另一种解决方案是使用flock(How can I ensure I have only one instance of a PHP script running via Apache?),以确保我的脚本只运行了一个实例 .

不过,我可以看到flock实现的许多问题 . 例如,如果我的脚本在调用 fclose($fp) 之前崩溃会发生什么?我的脚本的下一个实例是否能够继续,或者它会将其视为脚本运行的另一个实例(也就是 flock 函数将返回到脚本的新实例)?另一个问题是我的脚本逐个发送电子邮件 . 这意味着如果我有100封电子邮件要发送并且我在3.5分钟内发送它们,那么下一个实例将在第一个实例STARTS后4分钟开始 . 这意味着如果订户选择订阅第一个实例开始的那一刻,那么他们将不得不等待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) );

处理这个问题的正确方法是什么?

2 回答

  • 0

    在另一个表中创建一个名为“cron_running”的字段或类似的字段,并在cron脚本的顶部将其设置为true . 在脚本结束时将字段设置为false . 这将允许您检查cron脚本是否仍然在运行,如果它恰好重叠而不是继续 .

  • 0

    在阅读有关您的问题时,会出现许多问题:

    你说:

    我不希望他在显示消息之前等待服务器将确认电子邮件发送到他的地址 .

    首先,通过发送确认邮件添加的延迟应该非常短 .

    其次,订阅时事通讯对我来说就像是一封“电子邮件” . 你怎么能说,如果你没有得到一些确认,“订阅就好了”?

    您提到的另一个问题实际上归结为如何为数据库中的多个进程进行某些状态的簿记 - 在本例中为"email sent" . 鉴于您的数据库具有适当的isolation level,即您只能读取未更改的内容,数据库会处理此问题 .

    您的“占用”字段可以改为称为带有值的email_status,

    0 - new
    1 - processing
    2 - created
    3 - confirmed
    

    最后一点是,这本簿记只是暂时的状态 . 您不希望在数据库中保留所有这些冗余的“已确认”条目,只是为了从“新”到“已确认”的路上获取有关可能出错的信息 .

    所以你甚至可以使用一个不同的表,由id和status组成,其中最后一步处理是删除它们 . 这样,您的附加表将只包含未完全处理的ID .

相关问题