laravel中的队列一

一个队列最基本的功能应该是入站和出站。一边把任务放到队列中,一边从队列中读取处理任务。

我们看一下laravel中对队列的设计,首先是接口的设计在\Illuminate\Contracts\Queue\Queue

相关的接口被我用虚线分成了3类,第一类是push,也就是入站。第二类是pop也就是出战,第三类是读取队列大小,获取和设置ConnectionName。

其中入站的方法中,pushOn 和 laterOn 分别是 push 和 later 的别名。 bulk 是批量的 push  他们都是把job放到队列中,pushRaw指的是把原始数据放入到队列中。

举个例子来说明下这两类方法,用户123登录了,我需要进行数据统计可以有两种做法,

1、我们把123放入到队列中那么可以通过pushRaw这个方法把123放入队列,等处理程序拿到123再去处理,进行数据统计。但是在laravel中我们往往不这么处理,参考第二中做法

2、我们定义一个Job,用123去初始这个Job,然后把Job放入到队列中。这两种方式的差异在于一个放的是数据,一个放的是Job。laravel对队列的封装都是用的这种方式。在实际处理的时候,是把Job中的数据通过pushRaw放入到队列中的。

class Job 
{
   

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct($userId)
    {
        $this->userId = $userId;

    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
       //处理相关数据  统计数据加1
    }
}

 

接下里我们以Redis队列为例看下具体的处理过程

 

一、首先看一下抽象类的封装

抽象类的代码主要分3分布,图中用实线进行了分割,对应接口的3分布,一部分是关于队列的。接口中对入站的操作有pushOn laterOn push  later   bulk pushRaw 6个方法的定义。具体的如站和出站的方法,应该会每个驱动都不一样,所以在抽象类中只实现了 pushOn laterOn 和 bulk,而没有实现 push later pushRaw

/**
     * Push a new job onto the queue.
     *
     * @param  string  $queue
     * @param  string  $job
     * @param  mixed   $data
     * @return mixed
     */
    public function pushOn($queue, $job, $data = '')
    {
        return $this->push($job, $data, $queue);
    }

    /**
     * Push a new job onto the queue after a delay.
     *
     * @param  string  $queue
     * @param  \DateTimeInterface|\DateInterval|int  $delay
     * @param  string  $job
     * @param  mixed   $data
     * @return mixed
     */
    public function laterOn($queue, $delay, $job, $data = '')
    {
        return $this->later($delay, $job, $data, $queue);
    }

    /**
     * Push an array of jobs onto the queue.
     *
     * @param  array   $jobs
     * @param  mixed   $data
     * @param  string  $queue
     * @return mixed
     */
    public function bulk($jobs, $data = '', $queue = null)
    {
        foreach ((array) $jobs as $job) {
            $this->push($job, $data, $queue);
        }
    }

 

第二部分是createPayload,我们上面提到当把一个job放入队列的时候,实际是从job中提取信息,然后把信息通过pushRaw放入队列的。createPayload就是从job中读取信息以供pushRaw使用的。这一块的方法稍多一些,主要是createPayload通过createPayloadArray创建配置;createPayloadArray根据job的类型调用createObjectPayload或者createStringPayload。getDisplayName和getJobExpiration,而后者又需要相关的时间函数

第三部分是 getConnectionName 和 setConnectionName 的实现。size方法应该是每个驱动都不一样的。此外还提供了setContainer方法。

即成抽象类之后,队列必须实现的方法只剩下了push later pushRaw pop size。

二、RedisQueue中的具体实现

1、 push  pushRaw

public function push($job, $data = '', $queue = null)
    {
        return $this->pushRaw($this->createPayload($job, $data), $queue);
    }

push是通过createPayload整理数据之后,通过pushRaw写入队列。pushRaw的代码也非常简单,获取redis连接,通过getQueue获取队列,也就是redis中的key,然后用rpush命令插入到列表的最右边。

public function pushRaw($payload, $queue = null, array $options = []) { $this->getConnection()->rpush($this->getQueue($queue), $payload); return json_decode($payload, true)['id'] ?? null; }

 

2、later  laterRaw

和上面的方法对应later也是通过createPayload整理数据之后,通过laterRaw写入队列。

protected function laterRaw($delay, $payload, $queue = null)
    {
        $this->getConnection()->zadd(
            $this->getQueue($queue).':delayed', $this->availableAt($delay), $payload
        );

        return json_decode($payload, true)['id'] ?? null;
    }

对比pushRaw的方法,看到laterRaw在在原有队列的名称后补充了':delayed'后缀,以有序集合的方式存储。也就是说他们存储的不是一个地方。那么出栈的时候是怎么读取的呢?我们看下pop

3、pop

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

        list($job, $reserved) = $this->retrieveNextJob($prefixed);

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

这个的处理相对比较麻烦一些。$this->migrate($prefixed = $this->getQueue($queue));对队列进行了合并,然后从队列中读取任务,返回RedisJob

3.1 队列的合并

 

/**
     * 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);
        }
    }

    /**
     * Migrate the delayed jobs that are ready to the regular queue.
     *
     * @param  string  $from
     * @param  string  $to
     * @return array
     */
    public function migrateExpiredJobs($from, $to)
    {
        return $this->getConnection()->eval(
            LuaScripts::migrateExpiredJobs(), 2, $from, $to, $this->currentTime()
        );
    }

 

从代码中看到,首先是$queue和$queue.':delayed'的合并,然后对于设置了retryAfter的,再次用 $queue$queue.':reserved'进行合并。合并的Lua脚本:

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;
    }

每次对不超过100个的超时数据,从from,rpush到了to,也就是从$queue.':delayed'和 $queue.':reserved'插入了 $queue。从这里能看到redis队列对顺序的保证并不是特别准确。比如:

放入第1个消息a到队列,

延迟1s放入第2个消息b到队列,

然后过了1s再放入第3个消息c到队列。

如果在放入第3个消息c之前开启了队列的处理,输出的是abc,但是如果在放入第三个消息后开始的队列处理,那输出的就是acb了。

3、size

public function size($queue = null)
    {
        $queue = $this->getQueue($queue);

        return $this->getConnection()->eval(
            LuaScripts::size(), 3, $queue, $queue.':delayed', $queue.':reserved'
        );
    }

size的方法就相对简单了,对上面提到的3类消息$queue$queue.':delayed'和 $queue.':reserved'求和,其中$queue是队列,$queue.':delayed'是上面提到的延迟放入队列的消息, $queue.':reserved'是真正处理中的消息。

Redis队列用了很多的Lua脚本,来保证操作的原子性。

 

4、deleteReserved  deleteAndRelease

在处理成功之后需要通过deleteReserved删除保持的消息,在处理失败后通过deleteAndRelease删除保存的消息,并放回队列。

测试代码:

$redis = app(\Illuminate\Contracts\Queue\Factory::class)->connection('redis');
 //$redis->pushRaw("hi time is ".time());
$redis->push(new \App\Jobs\Test(123

dispatch((new \App\Jobs\Test(1));

 

转载于:https://www.cnblogs.com/coder5/p/9600823.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值