11

问题

我想知道是否可以在操作呈现视图时从其操作之一异步调用 Yii 控制器方法,从而使该方法完成长时间运行的操作。我很想做类似下面代码的事情,我不需要从my_long_running_func.

public function actionCreate() {
    $model = new Vacancies;
    if (isset($_POST['Vacancies'])) {
        $model->setAttributes($_POST['Vacancies']);
        $model->save();
        //I wish :)
        call_user_func_async('my_long_running_func',$model);
    }
    $this->render('create', array( 'model' => $model));
}

问题

我正在尝试在 Yii 中编写一个控制器操作,该操作发布一个空缺并通知该帖子的感兴趣的订阅者。问题是执行通知查询需要很长时间。

现在我正在寻找一种异步运行查询的方法,以便发布者在尽可能短的时间内看到他的响应,同时查询以类似于 C# 委托或事件的方式在后台运行。

我搜索的解决方案在控制器操作过程中执行异步请求,但我想做的只是异步运行控制器的方法,并且操作必须等到请求完成

已尝试

我尝试了以下方法,但是对于我大约 1500 个用户的测试数据,查询仍然很慢。

  • Yii 活动记录

    if ($vacancy->save()) {                
        if($vacancy->is_active == 1) {
            $url = Yii::app()->createUrl('vacancies/view',array('id'=>$model->id));
            $trainees = YumUser::getUsersByRole('Trainees');
            if($trainees!=null) {
                foreach($trainees as $trainee){
                    $message = new YumMessage;
                    $message->from_user_id = Yii::app()->user->id;
                    $message->title = 'Vacancy Notification: '.date('M j, Y');
                    $message->message = "A new vacancy has been posted at <a href='{$url}'>{$url}</a>.";
                    $message->to_user_id = $trainee->id;
                    $message->save();                
                }
            }
        }    
    }
    
  • Yii 数据访问对象

    if ($vacancy->save()) {        
        if($vacancy->is_active == 1) {
            $url = Yii::app()->createAbsoluteUrl('vacancies/view',array('id'=>$model->id));
            $trainee_ids=Yii::app()->db->createCommand()->select('user_id')->from('trainee')->queryColumn();
            $fid=Yii::app()->user->id;
            $msg="A new vacancy has been posted at <a href='{$url}'>{$url}</a>.";
            $ts = time();
            $tt = 'Vacancy Notification: '.date('M j, Y');
            if($trainee_ids!=null) {
                foreach($trainee_ids as $trainee_id){
                    Yii::app()->db->createCommand()
                      ->insert('message',array('timestamp'=>$ts,'from_user_id'=>$fid,'to_user_id'=>$tid,'title'=>$tt,'message'=>$msg));
                }
            }
        }
    }
    
  • 准备好的报表

    if ($vacancy->save()) {                
        if($vacancy->is_active == 1) {
            $url = Yii::app()->createUrl('vacancies/view',array('id'=>$model->id));                    
            $trainee_ids=Yii::app()->db->createCommand()->select('user_id')->from('trainee')->queryColumn();
            $fu=Yii::app()->user->id;
            $msg="A new vacancy has been posted at <a href='{$url}'>{$url}</a>.";
            $ts = time();
            $tt = 'Vacancy Notification: '.date('M j, Y');
            $sql="INSERT INTO message (timestamp,from_user_id,title,message,to_user_id) VALUES (:ts,:fu,:tt,:msg,:tu)";
            if($trainee_ids!=null) {
                foreach($trainee_ids as $trainee_id){
    
                    $command=Yii::app()->db->createCommand($sql);
                    $command->bindParam(":ts",$ts,PDO::PARAM_INT);
                    $command->bindParam(":fu",$fu,PDO::PARAM_INT);
                    $command->bindParam(":tt",$tt,PDO::PARAM_STR);
                    $command->bindParam(":msg",$msg,PDO::PARAM_STR);
                    $command->bindParam(":tu",$trainee_id,PDO::PARAM_INT);
    
                    $command->execute();
    
                }
            }
        }
    }
    

研究

我还检查了以下网站(我只允许发布两个链接),但它们要么需要等待请求完成的操作,要么需要 curl(我在部署服务器上无权访问)或需要一个外部库。我希望有一个原生的 PHP 实现。

编辑

通过以这种方式重写我的查询(将用户循环移动到数据库层),我能够大大减少响应时间:

public function actionCreate() {
    $user=YumUser::model()->findByPk(Yii::app()->user->id);
    $model = new Vacancies;
    $model->corporate_id=$user->professional->institution->corporate->id;
    $model->date_posted=date('Y-m-d');
    $model->last_modified=date('Y-m-d H:i:s');

    if (isset($_POST['Vacancies'])) {
        $model->setAttributes($_POST['Vacancies']);
        if ($model->save()) {                
            if($model->is_active == 1) {
                $url = Yii::app()->createAbsoluteUrl('vacancies/view',array('id'=>$model->id));                    
                $fu=Yii::app()->user->id;
                $msg="A new vacancy has been posted at <a href='{$url}'>{$url}</a>.";
                $ts = time();
                $tt = 'New Vacancy: '.$model->title;
                $sql='INSERT INTO message (timestamp,from_user_id,title,message,to_user_id) SELECT :ts,:fu,:tt,:msg,t.user_id FROM trainee t';
                Yii::app()->db->createCommand($sql)->execute(array(':ts'=>$ts,':fu'=>$fu,':tt'=>$tt,':msg'=>$msg));
            }                
            if (Yii::app()->getRequest()->getIsAjaxRequest())
                Yii::app()->end();
            else
                $this->redirect(array('view', 'id' => $model->id));
        }
    }
    $this->render('create', array( 'model' => $model));
}

尽管如此,如果有人可以发布一种异步调用函数的方法,那就太好了。

4

3 回答 3

4

通常,此类问题的解决方案是在您的系统中集成消息总线。您可以考虑像Beanstalkd这样的产品。这需要在您的服务器上安装软件。我想这个建议将被称为“使用外部库”。

如果您可以访问部署服务器并且可以添加 cronjob(或者系统管理员可以),您可以考虑使用 cronjob 对脚本执行 php-cli 调用,该脚本从由控制器填充的数据库中的作业队列中读取作业方法。

如果您无法在正在运行的服务器上安装软件,您可以考虑使用像Iron.io这样的 SAAS 解决方案来为您托管总线功能。Iron.io 正在使用所谓的推送队列。使用推送队列,消息总线主动向已注册的侦听器执行请求(推送)和消息内容。这可能有效,因为它不需要您执行 curl 请求。

如果以上都不可能,你的双手就被束缚了。与该主题非常相关的另一篇文章:Scalable, Delayed PHP Processing

于 2014-02-24T06:48:20.757 回答
2

我会试试这个,虽然我不是 100% 认为 Yii 会正常工作,但它相对简单且值得一试:

public function actionCreate() {
    $model = new Vacancies;
    if (isset($_POST['Vacancies'])) {
        $model->setAttributes($_POST['Vacancies']);
        $model->save();
        //I wish :)
    }

    HttpResponse::setContentType('text/html');
    HttpResponse::setData($this->render('create', array( 'model' => $model), true);
    HttpResponse::send();

    flush(); // writes the response out to the client

    if (isset($_POST['Vacancies'])) {
        call_user_func_async('my_long_running_func',$model);
    }
}
于 2012-04-23T17:46:17.907 回答
1

这是一种完全不同类型的建议。注册由CWebApplication 的 end() 函数触发的 onEndRequest 事件怎么样?

public function end($status=0, $exit=true)
{
    if($this->hasEventHandler('onEndRequest'))
        $this->onEndRequest(new CEvent($this));
    if($exit)
        exit($status);
}

您需要注册该事件并弄清楚如何以某种方式传递您的模型,但是在所有数据已刷新到浏览器后代码将正确运行......

于 2012-04-23T18:57:52.617 回答