Laravel 排队的作业即使有延迟也会立即处理

Posted

技术标签:

【中文标题】Laravel 排队的作业即使有延迟也会立即处理【英文标题】:Laravel queued jobs processed immediately even with a delay 【发布时间】:2015-09-14 11:47:16 【问题描述】:

我目前正在开发关于私人服务器的个人应用程序(例如,Minecraft 服务器),由于查询服务器需要一些时间,我决定实现排队作业。但是,它们不能正常工作,即使它们被延迟,它们也会在调用时立即运行,从而导致页面请求的大量延迟。

这是我的 HomeController 的 index(),它调用作业以延迟 30 秒更新每个服务器:

public function index()

    $servers = Server::all();

    foreach($servers as $server)
    
        // Job Dispatch
        $job = (new UpdateServer($server->id))->delay(30);
        $this->dispatch($job);
    
    return view('serverlist.index', compact('servers'));

更新服务器的作业类如下:

class UpdateServer extends Job implements SelfHandling, ShouldQueue

    use InteractsWithQueue, SerializesModels;
    protected $id;

    public function __construct($id)
    
        $this->id = $id;
    

    public function handle()
        $server = Server::findOrFail($this->id);

        // Preparing the packet
        $test = new RAGBuffer();
        $test->addChar('255');
        $test->addChar('1');
        $test->addShort(1 | 8);

        // Finding the server
        $serverGame = new RAGServer($server->server_ip);

        // Get server information
        $status = $serverGame->sendPacket($test);

        $server->onlinePlayers = $status->getOnline();
        $server->peakPlayers = $status->getPeak();
        $server->maxPlayers = $status->getMax();

        if (!$server->save()) 
            // Error occurred
        
    

每当 HomeController 的 index() 运行时,页面请求都会出现大量延迟。我按照 Laravel 官方网页上的教程,尝试寻找答案,但没有找到任何答案。

那么,我做错了什么?为什么作业没有延迟 30 秒,然后在我的服务器后台执行此操作?

另外:handle() 正在做它应该做的事情。它查询服务器、发送数据包并使用正确的信息更新我的数据库。

【问题讨论】:

我在使用 dispatch() 时遇到了问题。当我切换到\Queue::later(delay,job) 时,事情开始起作用了。 【参考方案1】:

这是创建用户 API 并将其历史存储在作业表中的完整步骤。 在 Jobs 类中:

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Repositories\Eloquent\ApiRepo as ApiRepo;
use Log;
use App\Models\User;

class UserProcess implements ShouldQueue

     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

     /**
     * The number of times the job may be attempted and override the queue tries.
     *
     * @var int
     */
    public $tries = 3;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public $user;

    public function __construct(User $user)
    
        $this->user = $user; 
    

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
           
        try 
            // make api call
           Log::info("inside handle".$this->user->id);
            $apiRepo = new ApiRepo;
            $response = $apiRepo->getUserDetails($this->user->id);
            Log::info("Response".$response);
         catch (\Throwable $exception) 
            if ($this->attempts() > 3) 
                // hard fail after 3 attempts
                throw $exception;
            
            // requeue this job to be executes
            // in 3 minutes (180 seconds) from now
            $this->release(180);
            return;
        
    


在控制器类中:

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Validator;
use App\Models\User;
use App\Jobs\UserProcess;
use App\Models\UserHistory;
use Carbon\Carbon;

class UserController extends Controller

    public function create(Request $request)
    
        $rules = [
            'first_name' => 'required|string|max:100',
            'last_name' => 'required|string|max:100',
            'email' => 'required|string|email|unique:users,email',
            'phone_number' => 'required|string|max:10',
            'address' => 'string',
            'date_of_birth' => 'string|date_format:Y-m-d|before:today',
            'is_vaccinated' => 'string|in:YES,NO',
            'vaccine_name' => 'string|required_if:is_vaccinated,==,YES|in:COVAXIN,COVISHIELD'
        ];

        $validator = Validator::make(array_map('trim', ($request->all())),$rules);

        if($validator->fails())
            return response()->json($validator->errors());       
        else
            $user = new User;
            $user->first_name = $request->first_name;
            $user->last_name = $request->last_name;
            $user->email = $request->email;
            $user->phone_number = $request->phone_number;
            $user->address = $request->address;
            $user->date_of_birth = $request->date_of_birth;
            $user->is_vaccinated = $request->is_vaccinated;
            $user->vaccine_name = $request->vaccine_name;
            $user->save();

            $token = $user->createToken('auth_token')->plainTextToken;
            if($user->save())
                $job = (new UserProcess($user))->delay(Carbon::now()->addMinutes(1));
                $this->dispatch($job);
            
                return response()
                ->json(['data' => $user,'status' => '200','message' => 'User Added Successfully','access_token' => $token, 'token_type' => 'Bearer']);
            else
                return response()
                ->json(['data' => $user,'status' => '409','message' => 'Something went wrong!']);
            
        
     

在 ApiRepo 类中:

namespace App\Repositories\Eloquent;

use App\Repositories\ApiInterface;
use Illuminate\Http\Request;
use App\Http\Requests;
use Illuminate\Http\Response;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\UserHistory;
use Log;

class ApiRepo implements ApiInterface 

     public function getUserDetails($userid) 
        Log::info('User ID - '.@$userid);
        $user_history = new UserHistory();
        $save_user = User::find($userid);
        Log::info('User Json Data - '.@$save_user);
        $user_history->user_id = $userid;
        $user_history->first_name = $save_user->first_name;
        $user_history->last_name = $save_user->last_name;
        $user_history->email = $save_user->email ;
        $user_history->phone_number = $save_user->phone_number;
        $user_history->address = $save_user->address;
        $user_history->date_of_birth = $save_user->date_of_birth;
        $user_history->is_vaccinated = $save_user->is_vaccinated;
        $user_history->vaccine_name = $save_user->vaccine_name;
        $user_history->save();
        if($user_history->save())
            Log::info('User history Saved!');
        
     

【讨论】:

【参考方案2】:

确保

'default' => env('QUEUE_DRIVER', 'database'), 

在文件 config/queue.php

还有

QUEUE_DRIVER=database 

在 .env 文件中以确保使用数据库驱动程序。

【讨论】:

【参考方案3】:

对于已经对之前的答案进行了更改但仍然无法正常工作的人,请检查队列文件的默认值,如下所示:dd(Config::get('queue.default'))

对我来说,在刷新配置缓存之前它没有改变:

php artisan config:clear

【讨论】:

对于本地开发,重启工匠服务器应该可以解决问题(我经常忘记这一点)【参考方案4】:

要在本地测试,您可以将驱动程序设置为

QUEUE_DRIVER=database

然后运行 ​​php artisan queue:table

然后php artisan migrate,这样您就可以将队列保存到数据库中,这样您就可以直观地看到发生了什么。

要运行您的队列,只需运行 php artisan queue:listen ... 并让它像使用 artisan serve 一样运行。

【讨论】:

必须运行queue:listen 命令才能使队列工作!谢谢【参考方案5】:

您必须在项目的根目录的.env 文件中设置要使用的队列驱动程序。

默认情况下,队列驱动程序是sync,它完全按照您的描述执行,立即执行队列。

您可以选择一些不同的队列驱动程序,例如 beanstalked 或 redis(这是我的选择)。 laracasts.com 上有一个excellent freebie,关于设置一个beantalked 队列。

要查看 Laravel 中所有可用的队列驱动程序选项,请查看 here

这是一个 .env 示例

APP_ENV=local
APP_DEBUG=true
APP_KEY=SomeRandomString

DB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync      // <-- Put the desired driver here

MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

【讨论】:

感谢您的回答,我想我会尝试 beanstalked 队列,所以,据我了解,它就像后台进程一样工作,对吧?它不会影响页面的加载时间或任何东西? 应该没有任何影响(除了带宽,如果你正在关闭或用你的脚本上传一些东西,但这不应该是一个问题)。 Beanstalked 是一个不错的选择,如果您按照视频在几分钟内完成设置。 太糟糕了,我没有 laracasts 的帐户,因为它是必需的,将尝试按照其他教程进行操作,一旦我开始工作,就会将您的问题标记为已接受,感谢您的宝贵时间! 这是另一个帮助我进行设置的好帖子。它提供了逐步配置的步骤。 scotch.io/tutorials/why-laravel-queues-are-awesome【参考方案6】:

就我而言,我必须实现 ShouldQueue 并使用 Queueable 特征:

class CustomNotification extends Notification implements ShouldQueue
    use Queueable;
...

【讨论】:

【参考方案7】:

这是因为延迟函数取的是未来的绝对日期

UpdateServer::dispatch($server->id)->delay(now()->addSeconds(30))

【讨论】:

【参考方案8】:

即使您已正确配置所有内容,这仍然可能发生。我们在 Laravel 5.4 中遇到了这个问题,我们创建了很多作业,有些是延迟的,并通过 Queue:bulk($jobs) 将它们添加到队列中。此调用以及Queue::push($job) 完全忽略delay 并导致立即处理作业。

如果您希望您的作业按照您的配置放入队列中,您必须调用dispatch($job)

【讨论】:

【参考方案9】:

在我意识到 Laravel 5.7 将 .env 文件中的 QUEUE_DRIVER 重命名为 QUEUE_CONNECTION 之前,这让我发疯了很久

【讨论】:

天哪。谢谢你。我也刚发生过。【参考方案10】:

如果您在 php artisan serve 上运行,请重新启动它并再次运行 php artisan serve。几个小时后,这对我有用,试图想知道它是什么。 :)

【讨论】:

【参考方案11】:

如果您通过 phpunit 对队列服务运行测试,请确保

<env name="QUEUE_DRIVER" value="X"/>

在 phpunit.xml 中不会覆盖您想要的队列驱动程序。

【讨论】:

以上是关于Laravel 排队的作业即使有延迟也会立即处理的主要内容,如果未能解决你的问题,请参考以下文章

Laravel - 自动执行排队的作业[重复]

rspec 中未处理延迟作业

Laravel 排队作业的问题。开发和生产的奇怪行为不同

如果从数据库中删除,Laravel 中的排队作业是不是会停止?

从内部重新排队或评估延迟的工作?

Laravel 尝试排队作业,触发新尝试的正确方法?