Laravel延迟队列的实现原理

Laravel延迟队列的实现原理

   redis的延迟队列一般是通过有序集合zset实现的,可参考:https://blog.csdn.net/raoxiaoya/article/details/103066624

实际上laravel的延迟队列也是这个原理。

  设置驱动为redis

  我们使用命令行来投递两个队列,一个普通队列,一个延迟队列

public function handle()
{
  $this->test1();
  $this->test2();
}

// 延迟执行
public function test2()
{
  $data = ['data' => Str::random(10), 'time' => date('Y-m-d H:i:s')];
  dispatch(new TestJob($data))->delay(now()->addMinute(20))->onConnection('redis')->onQueue('testqueue');
}

public function test1()
{
  $data = ['data' => Str::random(10), 'time' => date('Y-m-d H:i:s')];
  dispatch(new TestJob($data))->onConnection('redis')->onQueue('testqueue');
}

 

然后查看redis,发现多了两个key
1、queues:testqueue
  结构为list
  目前有一个元素

{"displayName":"App\\Jobs\\TestJob","job":"Illuminate\\Queue\\CallQueuedHandler@call","maxTries":null,"timeout":null,"timeoutAt":null,"data":{"commandName":"App\\Jobs\\TestJob","command":"O:16:\"App\\Jobs\\TestJob\":8:{s:4:\"data\";a:2:{s:4:\"data\";s:10:\"WKSmRQJsbs\";s:4:\"time\";s:19:\"2020-02-09 11:40:51\";}s:6:\"\u0000*\u0000job\";N;s:10:\"connection\";s:5:\"redis\";s:5:\"queue\";s:9:\"testqueue\";s:15:\"chainConnection\";N;s:10:\"chainQueue\";N;s:5:\"delay\";N;s:7:\"chained\";a:0:{}}"},"id":"0NB0RK9CKQRVbbuskTWUSs8Lp91XqYzW","attempts":0}

 

2、queues:testqueue:delayed
结构为zset
目前有一个元素

value:{"displayName":"App\\Jobs\\TestJob","job":"Illuminate\\Queue\\CallQueuedHandler@call","maxTries":null,"timeout":null,"timeoutAt":null,"data":{"commandName":"App\\Jobs\\TestJob","command":"O:16:\"App\\Jobs\\TestJob\":8:{s:4:\"data\";a:2:{s:4:\"data\";s:10:\"lIRuVmQsdK\";s:4:\"time\";s:19:\"2020-02-09 11:40:59\";}s:6:\"\u0000*\u0000job\";N;s:10:\"connection\";s:5:\"redis\";s:5:\"queue\";s:9:\"testqueue\";s:15:\"chainConnection\";N;s:10:\"chainQueue\";N;s:5:\"delay\";O:25:\"Illuminate\\Support\\Carbon\":3:{s:4:\"date\";s:26:\"2020-02-09 12:00:59.123314\";s:13:\"timezone_type\";i:3;s:8:\"timezone\";s:3:\"UTC\";}s:7:\"chained\";a:0:{}}"},"id":"8utPrwDVunKYZpol2DgQJdaalZy2szMc","attempts":0}

score: 1581249659

而Laravel是使用一个进程来消费所有队列的,那么它是怎样做的呢?

我们打开redis队列的具体实现:

 vendor/laravel/framework/src/Illuminate/Queue/RedisQueue.php

 

 

找到pop方法,该方法负责从队列中右弹出一个元素来消费的。

public function pop($queue = null)
{
  $this->migrate($prefixed = $this->getQueue($queue));

  if (empty($nextJob = $this->retrieveNextJob($prefixed))) {
    return;
  }

[$job, $reserved] = $nextJob;

if ($reserved) {
  return new RedisJob(
  $this->container, $this, $job,
  $reserved, $this->connectionName, $queue ?: $this->default
  );
  }
}

/**
* 迁移所有的 “时间到了” 的队列(包括延迟队列,重试时间到了的队列)到主队列的右侧即刻执行。
* Migrate any delayed or expired jobs onto the primary queue.
*
* @param string $queue
* @return void
*/
protected function migrate($queue)
{
  $this->migrateExpiredJobs($queue.':delayed', $queue);

  if (! is_null($this->retryAfter)) {
    $this->migrateExpiredJobs($queue.':reserved', $queue);
  }
}

public function migrateExpiredJobs($from, $to)
{
  return $this->getConnection()->eval(
    LuaScripts::migrateExpiredJobs(), 2, $from, $to, $this->currentTime()
  );
}

public static function migrateExpiredJobs()
{
  return <<<'LUA'
  -- Get all of the jobs with an expired "score"...
  local val = redis.call('zrangebyscore', KEYS[1], '-inf', ARGV[1])

  -- If we have values in the array, we will remove them from the first queue
  -- and add them onto the destination queue in chunks of 100, which moves
  -- all of the appropriate jobs onto the destination queue very safely.
  if(next(val) ~= nil) then
  redis.call('zremrangebyrank', KEYS[1], 0, #val - 1)

  for i = 1, #val, 100 do
  redis.call('rpush', KEYS[2], unpack(val, i, math.min(i+99, #val)))
  end
  end

  return val
  LUA;
}

 

————————————————
版权声明:本文为CSDN博主「raoxiaoya」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/raoxiaoya/article/details/104241467

 

 

posted @ 2021-09-26 14:58  笨笨韩  阅读(906)  评论(0)    收藏  举报