I am working on an app that requires fetching data from a third-party server and that server allows max 1 request per seconds.
Now, all request send as job and I am
You could use this package to use rate limiting with Redis or another source, like a file. Uses settings to set bucket size and rate as fractions of the time limit, so very small storage.
composer require bandwidth-throttle/token-bucket
https://github.com/bandwidth-throttle/token-bucket
It allows you to wrap the check in an if, so it will wait for a free token to be available, 1 a minute in your example. In effect, it makes the service sleep for the required amount of time until a new minute.
I am rate limiting a job que to max out at 100 jobs per day. I chose to use the 'delay' feature.
Job Class
<?php
namespace FuquIo\LaravelJobs\Orm;
use Illuminate\Database\Eloquent\Model;
class Job extends Model{
protected $dateFormat = 'U';
protected $dates = ['reserved_at', 'available_at'];
}
Sample delay without much context...
$que = config(ServiceProvider::SHORT_NAME . '.job-queue');
$preceeding_job = Job::whereQueue($que['name'])
->orderBy('available_at', 'DESC')
->first();
if(!empty($preceeding_job)){
$available = $preceeding_job->available_at;
}else{
$available = Carbon::now();
}
ProbeGoogle::dispatch($obj)
->onConnection($que['connection'])
->onQueue($que['name'])
->delay($available->addMinutes(15));
Note that completed jobs are removed from the jobs table, so you need another means of tracking them and a bit more logic to calculate $available. I use https://github.com/imTigger/laravel-job-status for that. For this example, I went with simple logic. It will either fire 15 min after the last job in the queue, or 15 min from now. That helps pace things out just in case the last job fired and disappeared 2 seconds ago.
I'm the author of mxl/laravel-queue-rate-limit Composer package.
It allows you to rate limit jobs on specific queue without using Redis.
Install it with:
$ composer require mxl/laravel-queue-rate-limit:^1.0
This package is compatible with Laravel 5.5+ and uses auto-discovery feature to add MichaelLedin\LaravelQueueRateLimit\QueueServiceProvider::class
to providers.
Add rate limit settings to config/queue.php
:
'rateLimit' => [
'mail' => [
'allows' => 1,
'every' => 5
]
]
These settings allow to run 1 job every 5 seconds on mail
queue.
Make sure that default queue driver (default
property in config/queue.php
) is set to any value except sync
.
Run queue worker with --queue mail
option:
$ php artisan queue:work --queue mail
You can run worker on multiple queues, but only queues referenced in rateLimit
setting will be rate limited:
$ php artisan qeueu:work --queue mail,default
Jobs on default
queue will be executed without rate limiting.
Queue some jobs to test rate limiting:
SomeJob::dispatch()->onQueue('mail');
SomeJob::dispatch()->onQueue('mail');
SomeJob::dispatch()->onQueue('mail');
SomeJob::dispatch();
If you need "throttling" and are not using Redis as your queue driver you can try to use the following code:
public function throttledJobDispatch( $delayInSeconds = 1 )
{
$lastJobDispatched = Cache::get('lastJobDispatched');
if( !$lastJobDispatched ) {
$delay_until = now();
} else {
if ($lastJobDispatched->addSeconds($delayInSeconds) < now()) {
$delay_until = now();
} else {
$delay_until = $lastJobDispatched->addSeconds($delayInSeconds);
}
}
Job::dispatch()->onQueue('YourQueue')->delay($delay_until);
Cache::put('lastJobDispatched', $delay_until, now()->addYears(1) );
}
What this code does is release a job to the queue and set the start time X seconds after the last dispatched job's start time. I successully tested this with database as queue-driver and file as cache driver.
There are two minor problems I have encountered so far:
1) When you use only 1 second as a delay, depending on your queue worker - the queue worker may actually only "wake up" once every couple of seconds. So, if it wakes up every 3 seconds, it will perform 3 jobs at once and then "sleep" 3 seconds again. But on average you will still only perform one job every second.
2) In Laravel 5.7 it is not possible to use Carbon to set the job delay to less than a second because it does not support milli- or microseconds yet. That should be possible with Laravel 5.8 - just use addMilliseconds
instead of addSeconds
.
spatie/laravel-rate-limited-job-middleware
This is a nice package if you are using laravel 6 or above. Nice thing is you can configure middleware in the job.
Install
composer require spatie/laravel-rate-limited-job-middleware
Assuming you have only single worker you can do something like this:
so basically:
doSomething()
$time = microtime(true);
usleep(1000 - ($time - LARAVEL_START));