Send Multiple SMS to users using Jobs - laravel

I have around 25K users, I want to send sms for each one of them.
I did the following Job,
<?php
namespace App\Jobs;
use App\User;
use App\Jobs\Job;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use GuzzleHttp\Client;
use GuzzleHttp\Post\PostBodyInterface;
use GuzzleHttp\Exception\ClientException;
class SendSMS extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
protected $user;
public function __construct($user)
{
$this->user = $user;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$client = new \GuzzleHttp\Client(['base_uri' => "example.com"]);
$messageBody = "Message";
$data = ["messageBody"=>$messageBody,
"msisdn"=>$this->user];
$client->request('POST', 'SendSMS',[ 'json'=>$data]);
}
}
And I am dispatching the jobs using JobController
<?php
namespace App\Http\Controllers;
use App\Jobs\SendSMS;
use Illuminate\Http\Request;
use App\User;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class JobController extends Controller
{
public function sendSMS()
{
$users = User::all();
foreach ($users as $user)
{
$job = (new SendSMS($user->mobile))->onQueue('sms');
$this->dispatch($job);
echo $player->mobile;
echo "<br/>";
}
}
}
And I ran the queue using the command line,
now I have two problems
first : the request is very slow, meaning I wait around 20 seconds for the http request to be done to go for the next user, can I send the request and go to the second user without waiting for the response from the sms api ?
second : can I run the dispatch without actually running the method in the controller, like I want the jobs to be queued using command line for example or any other approach.

Instead of creating a job for each user mobile, create a SendSMSToAllUsers job and your request will be very quick. The SendSMSToAllUsers job will fetch all users an dispatch a new job for each user.
To dispatch without actually running the method in the controller you only need to include the Illuminate\Foundation\Bus\DispatchesJobs trait in any of your classes (artisan commands, queue Jobs, etc...) and use $this->dispatch($job) method.
For better performance either use the async features of Guzzle or dispatch your SMS jobs to multiple queues and run in parallel several queue workers, one for each queue using the parameter --queue=myqueue. Having 25k users is a enough reason for using multiple queues.
For instance, you can create 10 queues: 'sms-0' for phone numbers ending in 0, 'sms-1' for phone numbers ending in 1, ... and so on. Or you can create two queues, 'sms-odd' and 'sms-even' for odd or even numbers.

Related

How to run MQTT subscribe in the background in Laravel

I am trying to put MQTT subscribe into a job saved in the database, but I get an error message that the job has failed instead of running for an infinite time. I would like to know if my approach is even possible and if there are any better alternatives to my problem.
My code
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 PhpMqtt\Client\Facades\MQTT;
class StartSub implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct()
{
//
}
public function handle()
{
$mqtt = MQTT::connection();
$mqtt->subscribe('topic', function (string $topic, string $message) {
echo sprintf('Received QoS level 1 message on topic [%s]: %s',
$topic, $message);
}, 1);
$mqtt->loop(true);
}
}
The answer is pretty simple. Although I do not know if it is 100% correct. After further investigation I found out that my code was failing because of time out. So i just had to put a public $timeout = 0;.

Passing parameters to Laravel job is not working

I've read solutions to this problem before but it seems that I'm doing something else wrong and that's why I'm asking: Usually the solution is adding the parameters to the body of the class as well as the __construct method but even doing that it doesn't work.
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Mail;
use App\Mail\TasksFinished;
class SendMailFinished implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* #return void
*/
public $tries = 3;
protected $msg;
protected $subj;
protected $mailto;
public function __construct($msg, $subj, $mailto)
{
//
$this->msg = $msg;
$this->subj = $subj;
$this->mailto = $mailto;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
//Envia correo cuando termine cola.
Mail::to($this->mailto)->queue(new TasksFinished($this->msg, $this->subj));
}
I try to run this through a queue this way in tinker:
use App\Jobs\SendMailFinished;
$job = new SendMailFinished('Hola', 'Prueba', 'ffuentes#example.org');
$job->dispatch();
TypeError: Too few arguments to function
App/Jobs/SendMailFinished::__construct(), 0 passed in
C:/laragon/www/reportes/vendor/laravel/framework/src/Illuminate/Foundation/Bus/Dispatchable.php
on line 16 and exactly 3 expected
Why even after specifying all the parameters both in the class and during instantiation it still cannot see any parameters when dispatching. (I've tried making params public as well but it's the same thing).
When you call dispatch() you should send the parameter there.
Try calling it statically
SendMailFinished::dispatch('Hola', 'Prueba', 'ffuentes#example.org');
There are 2 ways to pass parameters to a dispatching job in laravel.
First is simply call dispatch or dispatchNow as per requirement on your Job class like calling a static method on class:
YourJob::dispatch(argument1, argument2, argument3);
Second is simply pass arguments while creating an instance/object of Job class then pass object to dispatch method(always available in controller) like:
$this->dispatch(new YourJob(argument1, argument2, argument3));
The arguments will be available in the constructor, can you assign them to class local variable properties and use anywhere in Job class.

Laravel dispatch queue always intermediately run

I am trying to dispatch jobs in Laravel into mysql. If I do
dispatch(new SendBroadcastSMS());
or
SendBroadcastSMS::dispatch()->delay(now()->addMinutes(2));
The job always runs intermediately. In my job, I set to sleep 30 seconds and my current controller/page that calls dispatch also waiting for 30 seconds and echo the job return. Why? It should run in the background via worker right? Or I misunderstand with the laravel queue?
My job is like below:
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SendBroadcastSMS implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct()
{
}
public function handle()
{
sleep(30);
echo "test";
// dd($this->sendSms("6281517777767","test message"));
//
}
}
my queue driver is database mysql and I also not receieved any row in job table
QUEUE_DRIVER=database
QUEUE_DRIVER is not the property you should set in your .env file.
The correct property is QUEUE_CONNECTION.
QUEUE_CONNECTION=database

Laravel: How can a queued job move itself to failed_jobs on fail?

I have a queueable Job that creates a new user...
<?php
namespace App\Jobs;
...
class CreateNewUser implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** #var array */
public $newUserData;
public function __construct($newUserData)
{
$this->newUserData = $newUserData;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$email = $this->newUserData['email'];
if (User::whereEmail($email)->count()) {
// TODO: Make this job fail immediately
throw new UserWithEmailExistsException('User with email ' . $email . ' already exists');
}
...
}
}
It's a queued job because we batch process CSVs to load in many users at a time, each one requiring an entry in 2 tables plus multiple entries in roles and permissions tables. Too slow to do synchronously.
I have a check at the start of the handle() method to see if a user with the same email address has not already been created (because potentially several jobs could be queued to create users with the same email) and it throws a custom Exception if it does.
If that check fails, I don't ever want the queue worker to attempt this job again because I know it will continue to fail indefinitely, it's a waste of time to re-attempt even once more. How can I manually force this job to fail once and for all and move over to the failed jobs table?
P.S. I have found the SO answer about the fail() helper and the $this->markAsFailed() method but these still do not immediately move the job from jobs to failed_jobs.
I've completely rewritten my answer for you. I was in a rush last night and thought I had a quick solution to your problem, sorry for the misinformation.
You could do something like the code below, this won't put the job into the failed jobs table but it will prevent the re-attempts from happening. I hope that helps.
<?php
namespace App\Jobs;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class TestJob extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
public function __construct()
{
parent::__construct();
}
public function handle()
{
if ($this->attempts() > 0) {
// Will not reattempt this job.
return;
}
throw new \Exception('Job will fail.');
}
}

Have a queue job always running

I have a queue job that I need to have constantly running.
That means that when the job is finished processing, it should start the job all over again.
How can I do this?
Here's my job:
<?php
namespace App\Jobs;
use App\User;
use App\Post;
use App\Jobs\Job;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class PostJob extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function handle()
{
$posts = Post::where('user_id', $this->user->id)
->get();
foreach ($posts as $post) {
// perform actions
}
}
}
Here's the job controller:
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
public function startPostJob(Request $request, $id)
{
$users = User::all();
foreach ($users as $user) {
$this->dispatch(new PostJob($user));
}
}
}
The queue is meant for one time request, not continuous job running. Your architecture should probably move to more of a cron job setup so you can set intervals to re-run your desired code.
Have a look at the task scheduling documentation here https://laravel.com/docs/5.1/scheduling
The forever running Job, does not sound like the best idea, mostly because it could easily lead to a lot of cpu power being used. But i have done job chaining, which i achieved a certain way, and i think this can help you to solve your problem.
What i usually do, is make the responsibility of a Model specific job, the models responsibility. This will make it easier to invoke from the job.
public class User{
use DispatchesJobs; //must use, for dispatching jobs
...
public function startJob()
{
$this->dispatch(new PostJob($this));
}
}
This will give you the possibility of chaining the jobs together, after the job has ended. Remember to delete the job, after it has finished. And will continue for ever, by creating a new job each time.
public function handle()
{
...
foreach ($posts as $post) {
// perform actions
}
$this->user->startJob();
$this->delete();
}

Resources