php从多个正在运行的脚本访问数据库,无需重复选择


php access database from multiple running scripts without duplicate select

我有一个脚本,它正在将图片发布到Facebook个人资料中。它需要很长时间,通常在完成之前就会崩溃。我需要做的是从一个cron作业运行多个版本的脚本,同时进行许多处理,以便更快地发布。

我的问题是我希望同时运行多个脚本来加快发布速度。选择用户,并更新为已发送但是,我需要确保脚本不会试图同时处理两个用户,或者在另一个正在运行的脚本可能正在处理时从数据库中选择同一用户两次,从而导致两张照片被张贴在用户墙上。

我的问题涉及调用数据库的效率,以及创建一种同时在该数据库上运行多个脚本以提高发布速度的方法。

我有一个名为tweetSend的字段,该字段设置为notSent。发布照片后,它会更新为"已发送"。但这对每个帖子来说需要将近7秒的时间,如果另一个脚本在处理一个用户时访问了数据库,它会在用户墙上重复发布,因为表中的字段仍然是"notSent"

我的代码说选择一个tweetSend为"notSent"的用户。我有一个想法可以解决我的问题。我正在考虑立即将其更新为"发送",以便同时运行的另一个多个cron作业将选择此用户。然后,当它完成发布时,它将更新为"已发送"。

我还看到了我的代码的另一个问题。它似乎同时选择所有用户,然后运行WHILE循环并从此循环发布。在我看来,也许我应该选择一个"tweetSend"为"notSent"的用户,然后立即将其更新为"sending",在发送时更新为"sent"。也许把这个对数据库的调用放在一个函数中,然后在while循环中调用那个函数?不确定。

我对Stack上所有优秀的程序员的问题是,我想知道这是否是最好的方法,或者也许有人知道一种更好、更有效的方法来实现我想要实现的目标。

下面是我当前脚本的数据库部分,运行速度非常慢,如果没有双帖子,我将无法运行该脚本的多个版本。

$query = "SELECT * FROM `user` WHERE tokenExpireDate > '"$date'" AND tweetSent='notSent' ";

$retval = mysql_query($query) or die("MySQL Error: ".mysql_error());
while($row = mysql_fetch_array($retval))
{
    PostPhoto($fbId, $access_token);
    PostText($fbId, $access_token);

    echo "<hr>";
    $query = "UPDATE user SET tweetSent='sent' WHERE fbID='$fbId'";
    mysql_query($query) or die("MySQL Error: ".mysql_error());
}

更新:

我想出了一个"潜在"的解决方案。这行得通吗?还是愚蠢?当我在三个不同的窗口中同时运行脚本时,我似乎一直在工作它只按顺序运行它们,不能同时连接到数据库。看起来。

$query = "SELECT * FROM `user` WHERE tokenExpireDate > '"$date'" AND tweetSent='notSent' LIMIT 5";
$retval = mysql_query($query) or die("MySQL Error: ".mysql_error());
while($row = mysql_fetch_array($retval))
    {
        $ID[] = $row['fbID'];
    }
echo count($ID);
for($i=0; count($ID)>$i;$i++)
{   
sendTweet($ID[$i]);
}
function sendTweet($ID)
{
    // QUES THE USER ID
    $query = "SELECT * FROM `user` WHERE tokenExpireDate > '"$date'" AND tweetSent='notSent' AND fbID = '$ID' LIMIT 7";
    $retval = mysql_query($query) or die("MySQL Error: ".mysql_error());
    if (mysql_num_rows($retval) >= 1 ) 
    { 
            $query = "UPDATE user SET tweetSent='sending' WHERE fbID='$ID'";
            mysql_query($query) or die("MySQL Error: ".mysql_error());  
            while($row = mysql_fetch_array($retval))
            {
                $fbId = $row['fbID'];
                $type = $row['type'];
                $access_token = $row['longToken'];

                PostPhoto($fbId, $access_token);
                PostText($fbId, $access_token);

                /// UPDATES TO SENT
                $query = "UPDATE user SET tweetSent='sent' WHERE fbID='$fbId'";
            mysql_query($query) or die("MySQL Error: ".mysql_error());
        }
    }
}

几个建议:

  • 您需要了解流程中的瓶颈在哪里。只是猜测,我可以想象这将是Facebook的实际POST。你应该把精力集中在提高这一部分的效率上,否则你永远不会得到你想要的那么多吞吐量
  • 在用尽其他方法之前,不要使用多个重叠的cron脚本
  • 假设POST到Facebook是性能块,您可以考虑使用curl_mulit_exec()或类似方法并行调用Facebook。我在PHP中设置了一个简单的REST客户端类来完成这项工作。您可以随意使用它,也可以简单地查看curl_multi_exec()的实现,了解如何做到这一点。以下是链接-https://github.com/mikecbrant/php-rest-client
  • 在使用数据库时,应该考虑使用LIMIT语句。通过这种方式,您可以一次处理表的一个子集,在进入下一个子组之前更新该记录的成功POST。例如,假设您使用curl_multi_exec()一次向Facebook发布10条帖子,您可以从DB中一次选择10行,对这些行发出10个并行的Facebook请求,将发布结果更新到这些行,然后转到下一组记录
  • 您也可以考虑使用某种排队系统来实现这一点,因为这本质上是您在这里要做的就是构建一个队列。如果这对你来说是一项艰巨的任务,那么也许从上面的步骤开始,如果你发现这仍然不能满足你的需求,就转到队列中。当然,如果您希望对大量记录执行这种操作,您可能需要三思而后行,直接进入队列,因为使用关系数据库作为队列对于大流量来说并不理想

最好的解决方案是将照片发布系统更改为消息队列,因为它听起来非常像消息队列。

IronMQ是一个很好的起点,也是最容易使用的起点之一,从那里您可以探索其他队列系统。你还可以获得一个很棒的免费等级。其他的是RabbitMQ和AmazonSQS。

队列通过将消息从队列中删除或"弹出",然后进行处理来解决问题。如果发生错误或无法处理消息,则会将其放回队列中。

通过将它从队列中删除,您可以确保没有其他进程可以访问它

你可能还想看看Gearman。


如果您不想将系统更改为队列系统,那么您需要查看事务和记录锁定。将记录设置为processing可以允许两个进程同时提取记录,然后两个进程相继将其更新为processing,从而导致两个进程都发布照片。

单例:

private static $MysqlAdapter;

private $con;
private function __construct() {
$this->con = new mysqli(DB_SERVER, DB_USER, DB_PW, DB_DB);
if ($this->con->connect_errno) {
    echo "Failed to connect to MySQL: (" . $this->con->connect_errno . ") " . $this->con->connect_error;
} else {
    $this->con->set_charset("utf8");
}
}
public static function getInstance() {
if (self::$MysqlAdapter == NULL) {
    self::$MysqlAdapter = new MysqlAdapter();
}
return self::$MysqlAdapter;
}