event_queue

定位

event_queue 用來派發通知類任務。

常見通知類型:

  • email
  • SMS
  • app push
  • web push
  • MoEngage

寫入位置

寫入 model:

Lib/Model/EventQueue.php

寫入方式:

EventQueue::get()->insertEvent($eventKey, $eventTaskDO);

資料會寫入:

event_queue.event_key
event_queue.params
event_queue.priority

其中 params 通常是 EventTaskDO JSON,裡面會包含 tasks

消費 worker

worker:

Endpoint/V1/EventQueue.php::index()

啟動 script:

CLI/cron_event_queue.sh

worker 流程:

讀 event_queue
-> lock row
-> 複製到 event_queue_log_*
-> 刪除 event_queue row
-> 查 event_actions
-> NotificationFactory 建 handler
-> handler process()
-> 更新 event_queue_log_*.status_id / message

控制參數

event_queue worker 的版本控制參數放在:

event_queue_params.p_key = run_version

目前基準值:

idp_keyp_value
1run_version2

若有調整 queue 執行程式,且程式會依版本切換邏輯,要同步更新 run_versionp_value

完整控制參數請看 event_queue_params

event_queue_log 分表

EventQueue worker 會依時間決定 log table:

event_queue_log_1
event_queue_log_2
event_queue_log_3
event_queue_log_4

所以查 event log 時不要只查 event_queue_log

實務查詢時要把 event_queue_log_1event_queue_log_4 都納入。只查 event_queue 與舊的 event_queue_log 主表,會把已被 worker 成功處理的 notification 誤判成沒有送出。

Notification handler

dispatcher:

Lib/Util/Notification/NotificationFactory.php

注意:NotificationFactory::$handlers 是用 event_actions.template_type 找 handler,不是用 event_queue.event_key。例如 User_Change_Password 會先查 event_actions 得到 template_type=email,最後才走 EmailHandler。詳細說明見 event_actions

handler map:

template_typehandler
emailEmailHandler
smsSmsHandler
pushPushHandler
web_pushWebPushHandler
moengageMoengageHandler
fet_pushFetnetPushHandler

常用 SQL

查待處理 queue

SELECT
  id,
  created,
  event_key,
  params,
  priority,
  retry
FROM event_queue
WHERE event_key = '<event_key>'
ORDER BY id DESC
LIMIT 20;

查近期分表 log

SELECT
  id,
  inserted,
  executed,
  event_key,
  params,
  message,
  status_id,
  retry
FROM event_queue_log_1
WHERE event_key = '<event_key>'
ORDER BY id DESC
LIMIT 20;

實務上要依環境使用中的分表查 event_queue_log_1event_queue_log_4。為了避免漏查,建議直接 union 四張分片表:

SELECT * FROM (
  SELECT 'event_queue_log_1' AS log_table, id, inserted, executed, event_key, user_id, event_queue_id, status_id, retry, message, params
  FROM event_queue_log_1
  WHERE event_key = '<event_key>'
    AND params LIKE '%<quote_bid_id_or_request_id>%'
 
  UNION ALL
 
  SELECT 'event_queue_log_2' AS log_table, id, inserted, executed, event_key, user_id, event_queue_id, status_id, retry, message, params
  FROM event_queue_log_2
  WHERE event_key = '<event_key>'
    AND params LIKE '%<quote_bid_id_or_request_id>%'
 
  UNION ALL
 
  SELECT 'event_queue_log_3' AS log_table, id, inserted, executed, event_key, user_id, event_queue_id, status_id, retry, message, params
  FROM event_queue_log_3
  WHERE event_key = '<event_key>'
    AND params LIKE '%<quote_bid_id_or_request_id>%'
 
  UNION ALL
 
  SELECT 'event_queue_log_4' AS log_table, id, inserted, executed, event_key, user_id, event_queue_id, status_id, retry, message, params
  FROM event_queue_log_4
  WHERE event_key = '<event_key>'
    AND params LIKE '%<quote_bid_id_or_request_id>%'
) logs
ORDER BY inserted DESC
LIMIT 20;

查下游通知產物

如果 event_queue_log_* 顯示 success,也可以用下游表佐證:

通知類型常見佐證表常用線索
emailemail_logs, email_queueto, subject, template_key, sent_time
SMSsms_logsphone, result
pushpush_logs, push_sent_logs, fetnet_push_logsname, user_id, template, result

注意:push_logs 多半是 daily counter,不一定能精準對到單一 bid;精準佐證優先看 event_queue_log_*.params / message,再看 push_sent_logs 或 provider-specific push log。

重跑 event_queue_wait

event_queue_wait 用來暫存 retry 太多次、或第三方服務異常時先移出的 notification 事件。

相關 worker:

Endpoint/V1/EventQueue.php::retry_wait_event_queue()

內建重跑條件:

retry < 7
AND created < NOW() - INTERVAL 30 MINUTE
AND created > NOW() - INTERVAL 1 DAY

因此超過 1 天的 event_queue_wait row 不會被內建 retry 自動撈回,需要人工確認後手動搬回 event_queue

先確認 event_queue worker 版本:

SELECT id, p_key, p_value
FROM event_queue_params
WHERE p_key = 'run_version'
LIMIT 1;

若只是重跑 wait row,通常不需要更新 run_version。只有調整 queue worker 程式、且程式會依版本切換邏輯時,才更新 run_version

確認要重跑的 wait rows:

SELECT id, created, event_key, params, priority, retry
FROM event_queue_wait
WHERE id IN (<event_queue_wait_ids>)
ORDER BY id;

手動搬回 event_queue

START TRANSACTION;
 
INSERT INTO event_queue (
  created,
  event_key,
  params,
  priority,
  retry
)
SELECT
  NOW(),
  event_key,
  params,
  priority,
  retry
FROM event_queue_wait
WHERE id IN (<event_queue_wait_ids>);
 
DELETE FROM event_queue_wait
WHERE id IN (<event_queue_wait_ids>);
 
COMMIT;

確認已進 queue:

SELECT id, created, event_key, params, priority, retry
FROM event_queue
WHERE created >= NOW() - INTERVAL 5 MINUTE
  AND event_key IN (<event_keys>)
ORDER BY id DESC;

重跑時建議保留原 retry,不要重設為 1。這些 row 通常已失敗多次,保留 retry 可以避免第三方服務尚未恢復時無限重送;若再次失敗,worker 會依原流程提高 retry 並再次放回 event_queue_wait

排查重點

  • event_queue row 可能很快被搬到 event_queue_log_*
  • 測試機若有 cron / worker 正在跑,event_queue row 可能送出後立刻被派發通知。
  • event 是否會送 email / SMS / push,要查 event_actions
  • handler 失敗時看 event_queue_log_*.message 與 application log channel EventQueue_index
  • event_queue_log_*.message 中各 template value 為 true 時,代表該 handler 成功;若是字串錯誤,代表該 template 沒有設定或 handler 失敗。
  • 查 notification payload 時要從 params.tasks 看實際 task,不要只看 event_actions
  • 調整 queue worker 程式後,要確認 event_queue_params.run_versionp_value 是否符合新版程式預期。
  • event_queue_wait 內建 retry 只撈 30 分鐘以上、1 天以內、retry < 7 的 row;超過 1 天要人工確認後手動搬回。

實測誤判案例

provider_accept_narrow_match staging 實測中,quote_bid_id = 2147684964 的主流程與通知都正常:

event_key = Receive_New_Leads_Quote
status_id = 1
log table = event_queue_log_2

一開始只查:

event_queue
event_queue_log

會查不到 notification,因為 worker 已消費 event_queue,且 log 寫在 event_queue_log_2。正確查法是:

event_queue
event_queue_log_1
event_queue_log_2
event_queue_log_3
event_queue_log_4

該案例的 event_queue_log_2.message 顯示:

email.112 Accept New Leads Notification = true
sms.sms.new.leads.consumer = true
web_push.push.content.new.leads.consumer = true
push.push.content.new.leads.consumer = true

並且可從 email_logs / sms_logs / push_logs 看到下游產物。