佳木斯湛栽影视文化发展公司

主頁 > 知識(shí)庫 > 源碼分析 Laravel 重復(fù)執(zhí)行同一個(gè)隊(duì)列任務(wù)的原因

源碼分析 Laravel 重復(fù)執(zhí)行同一個(gè)隊(duì)列任務(wù)的原因

熱門標(biāo)簽:銀行業(yè)務(wù) 團(tuán)購網(wǎng)站 Mysql連接數(shù)設(shè)置 科大訊飛語音識(shí)別系統(tǒng) 電子圍欄 Linux服務(wù)器 服務(wù)器配置 阿里云

前言

laravel 的隊(duì)列服務(wù)對(duì)各種不同的后臺(tái)隊(duì)列服務(wù)提供了統(tǒng)一的 API。隊(duì)列允許你延遲執(zhí)行消耗時(shí)間的任務(wù),比如發(fā)送一封郵件。這樣可以有效的降低請(qǐng)求響應(yīng)的時(shí)間。

發(fā)現(xiàn)問題

在 Laravel 中使用 Redis 處理隊(duì)列任務(wù),框架提供的功能非常強(qiáng)大,但是最近遇到一個(gè)問題,就是發(fā)現(xiàn)一個(gè)任務(wù)被多次執(zhí)行,這是為什么呢?

先說原因:

因?yàn)樵?Laravel 中如果一個(gè)隊(duì)列(任務(wù))執(zhí)行時(shí)間大于 60 秒,就會(huì)被認(rèn)為執(zhí)行失敗并重新加入隊(duì)列中,這樣就會(huì)導(dǎo)致重復(fù)執(zhí)行同一個(gè)任務(wù)。

這個(gè)任務(wù)的邏輯就是給用戶推送內(nèi)容,需要根據(jù)隊(duì)列內(nèi)容取出用戶并遍歷,通過請(qǐng)求后端 HTTP 接口發(fā)送。比如有 10000 個(gè)用戶,在用戶數(shù)量多或接口處理速度沒那么快的情況下,執(zhí)行時(shí)間肯定會(huì)大于 60 秒,于是這個(gè)任務(wù)就被重新加入隊(duì)列。情況更糟糕一點(diǎn),前面的任務(wù)如果都沒有在 60 秒執(zhí)行完,就都會(huì)重新加入隊(duì)列,這樣同一個(gè)任務(wù)就不止重復(fù)執(zhí)行一次了,而是多次。

下面從 Laravel 源代碼找一下罪魁禍?zhǔn)住?/strong>

源代碼文件:vendor/laravel/framework/src/Illuminate/Queue/RedisQueue.php

/**
 * The expiration time of a job.
 *
 * @var int|null
 */
protected $expire = 60;

這個(gè) $expire 成員變量是一個(gè)固定的值,Laravel 認(rèn)為一個(gè)隊(duì)列再怎么 60 秒也該執(zhí)行完了吧。取隊(duì)列方法:

public function pop($queue = null)
{
 $original = $queue ?: $this->default; 
 $queue = $this->getQueue($queue); 
 $this->migrateExpiredJobs($queue.':delayed', $queue); 
 if (! is_null($this->expire)) {
  $this->migrateExpiredJobs($queue.':reserved', $queue);
 } 
 list($job, $reserved) = $this->getConnection()->eval(
  LuaScripts::pop(), 2, $queue, $queue.':reserved', $this->getTime() + $this->expire
 ); 
 if ($reserved) {
  return new RedisJob($this->container, $this, $job, $reserved, $original);
 }
}

取隊(duì)列有幾步操作,因?yàn)殛?duì)列執(zhí)行失敗,或執(zhí)行超時(shí)等都會(huì)放入另外的集合保存起來,以便重試,過程如下:

    1.把因執(zhí)行失敗的隊(duì)列從 delayed 集合重新 rpush 到當(dāng)前執(zhí)行的隊(duì)列中。

    2.把因執(zhí)行超時(shí)的隊(duì)列從 reserved 集合重新 rpush 到當(dāng)前執(zhí)行的隊(duì)列中。

    3.然后才是從隊(duì)列中取任務(wù)開始執(zhí)行,同時(shí)把隊(duì)列放入 reserved 的有序集合。

這里使用了 eval 命令執(zhí)行這個(gè)過程,用到了幾個(gè) lua 腳本。

從要執(zhí)行的隊(duì)列中取任務(wù):

local job = redis.call('lpop', KEYS[1])
local reserved = false
if(job ~= false) then
 reserved = cjson.decode(job)
 reserved['attempts'] = reserved['attempts'] + 1
 reserved = cjson.encode(reserved)
 redis.call('zadd', KEYS[2], ARGV[1], reserved)
end
return {job, reserved}

可以看到 Laravel 在取 Redis 要執(zhí)行的隊(duì)列的時(shí)候,同時(shí)會(huì)放一份到一個(gè)有序集合中,并使用過期時(shí)間戳作為分值。

只有當(dāng)這個(gè)任務(wù)完成后,再把有序集合中這個(gè)任務(wù)移除。從這個(gè)有序集合移除隊(duì)列的代碼就省略,我們看一下 Laravel 如何處理執(zhí)行時(shí)間大于 60 秒的隊(duì)列。

也就是這段 lua 腳本執(zhí)行的操作:

local val = redis.call('zrangebyscore', KEYS[1], '-inf', ARGV[1])
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 true

這里 zrangebyscore 找出分值從無限小到當(dāng)前時(shí)間戳的元素,也就是 60 秒之前加入到集合的任務(wù),然后通過 zremrangebyrank 從集合移除這些元素并 rpush 到隊(duì)列中。

看到這里應(yīng)該就恍然大悟了。

如果一個(gè)隊(duì)列 60 秒沒執(zhí)行完,那么進(jìn)程在取隊(duì)列的時(shí)候從 reserved 集合中把這些任務(wù)又重新 rpush 到隊(duì)列中。

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

您可能感興趣的文章:
  • Laravel學(xué)習(xí)教程之從入口到輸出過程詳解
  • Laravel框架源碼解析之反射的使用詳解
  • Laravel源碼解析之路由的使用和示例詳解
  • 通過源碼解析Laravel的依賴注入
  • Laravel框架數(shù)據(jù)庫CURD操作、連貫操作總結(jié)
  • PHP開發(fā)框架Laravel數(shù)據(jù)庫操作方法總結(jié)
  • Laravel框架表單驗(yàn)證詳解
  • Laravel框架中擴(kuò)展函數(shù)、擴(kuò)展自定義類的方法
  • Laravel框架路由配置總結(jié)、設(shè)置技巧大全
  • PHP的Laravel框架中使用消息隊(duì)列queue及異步隊(duì)列的方法
  • PHP框架Laravel插件Pagination實(shí)現(xiàn)自定義分頁
  • Laravel 5框架學(xué)習(xí)之向視圖傳送數(shù)據(jù)
  • Laravel框架源碼解析之入口文件原理分析

標(biāo)簽:蚌埠 棗莊 衢州 衡水 萍鄉(xiāng) 大理 廣元 江蘇

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《源碼分析 Laravel 重復(fù)執(zhí)行同一個(gè)隊(duì)列任務(wù)的原因》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266
    临江市| 马边| 桑植县| 绍兴县| 乐安县| 浦江县| 永泰县| 乌海市| 邵阳市| 阳新县| 漠河县| 屏东市| 同仁县| 固始县| 安阳县| 平谷区| 澳门| 榆社县| 锡林郭勒盟| 伊吾县| 赤峰市| 赤壁市| 大石桥市| 淮阳县| 海南省| 五家渠市| 巨野县| 土默特左旗| 池州市| 来安县| 开鲁县| 库车县| 平远县| 虎林市| 皋兰县| 威远县| 大兴区| 东平县| 北流市| 雷波县| 马公市|